Added filesystem callback support

Now a library like PhysFS can be used to load files by defining custom
callbacks and disabling the builtin ones by #define TINYGLTF_NO_FS
This commit is contained in:
Paolo Jovon 2018-07-07 20:43:33 +02:00
parent 90e2c9cc74
commit e6601bfb4b

View File

@ -754,6 +754,67 @@ bool WriteImageData(const std::string *basepath, const std::string *filename,
Image *image, bool embedImages, void *); Image *image, bool embedImages, void *);
#endif #endif
///
/// FilExistsFunction type. Signature for custom filesystem callbacks.
///
typedef bool (*FileExistsFunction)(const std::string &abs_filename,
void *);
///
/// ExpandFilePathFunction type. Signature for custom filesystem callbacks.
///
typedef std::string (*ExpandFilePathFunction)(const std::string &,
void *);
///
/// ReadWholeFileFunction type. Signature for custom filesystem callbacks.
///
typedef bool (*ReadWholeFileFunction)(std::vector<unsigned char> *,
std::string *,
const std::string &,
void *);
///
/// WriteWholeFileFunction type. Signature for custom filesystem callbacks.
///
typedef bool (*WriteWholeFileFunction)(std::string *,
const std::string &,
const std::vector<unsigned char> &,
void *);
///
/// A structure containing all required filesystem callbacks and a pointer to
/// their user data.
///
struct FsCallbacks
{
FileExistsFunction FileExists;
ExpandFilePathFunction ExpandFilePath;
ReadWholeFileFunction ReadWholeFile;
WriteWholeFileFunction WriteWholeFile;
void* user_data = nullptr; // An argument that is passed to all fs callbacks
};
#ifndef TINYGLTF_NO_FS
// Declaration of default filesystem callbacks
bool FileExists(const std::string &abs_filename,
void *);
std::string ExpandFilePath(const std::string &filepath,
void *);
bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
const std::string &filepath,
void *);
bool WriteWholeFile(std::string *err,
const std::string &filepath,
const std::vector<unsigned char> &contents,
void *);
#endif
class TinyGLTF { class TinyGLTF {
public: public:
#ifdef __clang__ #ifdef __clang__
@ -823,6 +884,11 @@ class TinyGLTF {
/// ///
void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data); void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data);
///
/// Set callbacks to use for filesystem (fs) access and their user data
///
void SetFsCallbacks(FsCallbacks callbacks);
private: private:
/// ///
/// Loads glTF asset from string(memory). /// Loads glTF asset from string(memory).
@ -837,21 +903,40 @@ class TinyGLTF {
size_t bin_size_; size_t bin_size_;
bool is_binary_; bool is_binary_;
FsCallbacks fs = {
#ifndef TINYGLTF_NO_FS
&tinygltf::FileExists,
&tinygltf::ExpandFilePath,
&tinygltf::ReadWholeFile,
&tinygltf::WriteWholeFile,
nullptr // Fs callback user data
#else
nullptr,
nullptr,
nullptr,
nullptr,
nullptr // Fs callback user data
#endif
};
LoadImageDataFunction LoadImageData = LoadImageDataFunction LoadImageData =
#ifndef TINYGLTF_NO_STB_IMAGE #ifndef TINYGLTF_NO_STB_IMAGE
&tinygltf::LoadImageData; &tinygltf::LoadImageData;
#else #else
nullptr; nullptr;
#endif #endif
void *load_image_user_data_ = nullptr; void *load_image_user_data_ = reinterpret_cast<void *>(&fs);
WriteImageDataFunction WriteImageData = WriteImageDataFunction WriteImageData =
#ifndef TINYGLTF_NO_STB_IMAGE_WRITE #ifndef TINYGLTF_NO_STB_IMAGE_WRITE
&tinygltf::WriteImageData; &tinygltf::WriteImageData;
#else #else
nullptr; nullptr;
#endif #endif
void *write_image_user_data_ = nullptr; void *write_image_user_data_ = reinterpret_cast<void *>(&fs);
}; };
#ifdef __clang__ #ifdef __clang__
@ -865,7 +950,9 @@ class TinyGLTF {
#ifdef TINYGLTF_IMPLEMENTATION #ifdef TINYGLTF_IMPLEMENTATION
#include <algorithm> #include <algorithm>
//#include <cassert> //#include <cassert>
#ifndef TINYGLTF_NO_FS
#include <fstream> #include <fstream>
#endif
#include <sstream> #include <sstream>
#ifdef __clang__ #ifdef __clang__
@ -966,74 +1053,6 @@ static void swap4(unsigned int *val) {
#endif #endif
} }
static bool FileExists(const std::string &abs_filename) {
bool ret;
#ifdef _WIN32
FILE *fp;
errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb");
if (err != 0) {
return false;
}
#else
FILE *fp = fopen(abs_filename.c_str(), "rb");
#endif
if (fp) {
ret = true;
fclose(fp);
} else {
ret = false;
}
return ret;
}
static std::string ExpandFilePath(const std::string &filepath) {
#ifdef _WIN32
DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0);
char *str = new char[len];
ExpandEnvironmentStringsA(filepath.c_str(), str, len);
std::string s(str);
delete[] str;
return s;
#else
#if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \
defined(__ANDROID__)
// no expansion
std::string s = filepath;
#else
std::string s;
wordexp_t p;
if (filepath.empty()) {
return "";
}
// char** w;
int ret = wordexp(filepath.c_str(), &p, 0);
if (ret) {
// err
s = filepath;
return s;
}
// Use first element only.
if (p.we_wordv) {
s = std::string(p.we_wordv[0]);
wordfree(&p);
} else {
s = filepath;
}
#endif
return s;
#endif
}
static std::string JoinPath(const std::string &path0, static std::string JoinPath(const std::string &path0,
const std::string &path1) { const std::string &path1) {
if (path0.empty()) { if (path0.empty()) {
@ -1050,10 +1069,20 @@ static std::string JoinPath(const std::string &path0,
} }
static std::string FindFile(const std::vector<std::string> &paths, static std::string FindFile(const std::vector<std::string> &paths,
const std::string &filepath) { const std::string &filepath,
FsCallbacks* fs) {
if (fs == nullptr || fs->ExpandFilePath == nullptr ||
fs->FileExists == nullptr) {
// Error, fs callback[s] missing
return std::string();
}
for (size_t i = 0; i < paths.size(); i++) { for (size_t i = 0; i < paths.size(); i++) {
std::string absPath = ExpandFilePath(JoinPath(paths[i], filepath)); std::string absPath = fs->ExpandFilePath(JoinPath(paths[i], filepath),
if (FileExists(absPath)) { fs->user_data);
if (fs->FileExists(absPath, fs->user_data)) {
return absPath; return absPath;
} }
} }
@ -1216,14 +1245,26 @@ std::string base64_decode(std::string const &encoded_string) {
static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err, static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
const std::string &filename, const std::string &filename,
const std::string &basedir, size_t reqBytes, const std::string &basedir, size_t reqBytes,
bool checkSize) { bool checkSize,
FsCallbacks *fs) {
if (fs == nullptr || fs->FileExists == nullptr
|| fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) {
// This is a developer error, assert() ?
if (err) {
(*err) += "FS callback[s] not set\n";
}
return false;
}
out->clear(); out->clear();
std::vector<std::string> paths; std::vector<std::string> paths;
paths.push_back(basedir); paths.push_back(basedir);
paths.push_back("."); paths.push_back(".");
std::string filepath = FindFile(paths, filename); std::string filepath = FindFile(paths, filename, fs);
if (filepath.empty() || filename.empty()) { if (filepath.empty() || filename.empty()) {
if (err) { if (err) {
(*err) += "File not found : " + filename + "\n"; (*err) += "File not found : " + filename + "\n";
@ -1231,31 +1272,22 @@ static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
return false; return false;
} }
std::ifstream f(filepath.c_str(), std::ifstream::binary); std::vector<unsigned char> buf;
if (!f) { std::string fileReadErr;
bool fileRead = fs->ReadWholeFile(&buf, &fileReadErr, filepath,
fs->user_data);
if (!fileRead) {
if (err) { if (err) {
(*err) += "File open error : " + filepath + "\n"; (*err) += "File read error : " + filepath + " : " + fileReadErr + "\n";
} }
return false; return false;
} }
f.seekg(0, f.end); size_t sz = buf.size();
size_t sz = static_cast<size_t>(f.tellg());
if (int(sz) < 0) {
// Looks reading directory, not a file.
return false;
}
if (sz == 0) { if (sz == 0) {
// Invalid file size. (*err) += "File is empty : " + filepath + "\n";
return false; return false;
} }
std::vector<unsigned char> buf(sz);
f.seekg(0, f.beg);
f.read(reinterpret_cast<char *>(&buf.at(0)),
static_cast<std::streamsize>(sz));
f.close();
if (checkSize) { if (checkSize) {
if (reqBytes == sz) { if (reqBytes == sz) {
@ -1356,30 +1388,34 @@ static void WriteToMemory_stbi(void *context, void *data, int size) {
} }
bool WriteImageData(const std::string *basepath, const std::string *filename, bool WriteImageData(const std::string *basepath, const std::string *filename,
Image *image, bool embedImages, void *) { Image *image, bool embedImages, void *fsPtr) {
const std::string ext = GetFilePathExtension(*filename); const std::string ext = GetFilePathExtension(*filename);
if (embedImages) { // Write image to temporary buffer
// Write image to memory and embed in output std::string header;
std::string header; std::vector<unsigned char> data;
std::vector<unsigned char> data;
if (ext == "png") { if (ext == "png") {
stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width, stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width,
image->height, image->component, &image->image[0], image->height, image->component, &image->image[0],
0); 0);
header = "data:image/png;base64,"; header = "data:image/png;base64,";
} else if (ext == "jpg") { } else if (ext == "jpg") {
stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width, stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width,
image->height, image->component, &image->image[0], image->height, image->component, &image->image[0],
100); 100);
header = "data:image/jpeg;base64,"; header = "data:image/jpeg;base64,";
} else if (ext == "bmp") { } else if (ext == "bmp") {
stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width, stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width,
image->height, image->component, &image->image[0]); image->height, image->component, &image->image[0]);
header = "data:image/bmp;base64,"; header = "data:image/bmp;base64,";
} } else if (!embedImages) {
// Error: can't output requested format to file
return false;
}
if(embedImages) {
// Embed base64-encoded image into URI
if (data.size()) { if (data.size()) {
image->uri = image->uri =
header + header +
@ -1389,21 +1425,16 @@ bool WriteImageData(const std::string *basepath, const std::string *filename,
} }
} else { } else {
// Write image to disc // Write image to disc
FsCallbacks *fs = reinterpret_cast<FsCallbacks *>(fsPtr);
const std::string imagefilepath = JoinPath(*basepath, *filename); if (fs != nullptr && fs->WriteWholeFile == nullptr)
if (ext == "png") { {
stbi_write_png(imagefilepath.c_str(), image->width, image->height, const std::string imagefilepath = JoinPath(*basepath, *filename);
image->component, &image->image[0], 0); std::string writeError;
} else if (ext == "jpg") { if(!fs->WriteWholeFile(&writeError, imagefilepath, data, fs->user_data)) {
// TODO (Bowald): Give user the option to set output quality? // Could not write image file to disc; Throw error ?
const int quality = 100; }
stbi_write_jpg(imagefilepath.c_str(), image->width, image->height,
image->component, &image->image[0], quality);
} else if (ext == "bmp") {
stbi_write_bmp(imagefilepath.c_str(), image->width, image->height,
image->component, &image->image[0]);
} else { } else {
// Throw error? Cant output requested format. // Throw error?
} }
image->uri = *filename; image->uri = *filename;
} }
@ -1412,6 +1443,145 @@ bool WriteImageData(const std::string *basepath, const std::string *filename,
} }
#endif #endif
void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks)
{
fs = callbacks;
}
#ifndef TINYGLTF_NO_FS
// Default implementations of filesystem functions
bool FileExists(const std::string &abs_filename, void *) {
bool ret;
#ifdef _WIN32
FILE *fp;
errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb");
if (err != 0) {
return false;
}
#else
FILE *fp = fopen(abs_filename.c_str(), "rb");
#endif
if (fp) {
ret = true;
fclose(fp);
} else {
ret = false;
}
return ret;
}
std::string ExpandFilePath(const std::string &filepath, void *) {
#ifdef _WIN32
DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0);
char *str = new char[len];
ExpandEnvironmentStringsA(filepath.c_str(), str, len);
std::string s(str);
delete[] str;
return s;
#else
#if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \
defined(__ANDROID__)
// no expansion
std::string s = filepath;
#else
std::string s;
wordexp_t p;
if (filepath.empty()) {
return "";
}
// char** w;
int ret = wordexp(filepath.c_str(), &p, 0);
if (ret) {
// err
s = filepath;
return s;
}
// Use first element only.
if (p.we_wordv) {
s = std::string(p.we_wordv[0]);
wordfree(&p);
} else {
s = filepath;
}
#endif
return s;
#endif
}
bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
const std::string &filepath, void *) {
std::ifstream f(filepath.c_str(), std::ifstream::binary);
if (!f) {
if (err) {
(*err) += "File open error : " + filepath + "\n";
}
return false;
}
f.seekg(0, f.end);
size_t sz = static_cast<size_t>(f.tellg());
f.seekg(0, f.beg);
if (int(sz) < 0) {
if (err) {
(*err) += "Invalid file size : " + filepath + " (does the path point to a directory?)";
}
return false;
} else if (sz == 0) {
if (err) {
(*err) += "File is empty : " + filepath + "\n";
}
return false;
}
out->resize(sz);
f.read(reinterpret_cast<char *>(&out->at(0)),
static_cast<std::streamsize>(sz));
f.close();
return true;
}
bool WriteWholeFile(std::string *err,
const std::string &filepath,
const std::vector<unsigned char> &contents,
void *) {
std::ofstream f(filepath.c_str(), std::ofstream::binary);
if (!f) {
if (err) {
(*err) += "File open error for writing : " + filepath + "\n";
}
return false;
}
f.write(reinterpret_cast<const char *>(&contents.at(0)),
static_cast<std::streamsize>(contents.size()));
if (!f) {
if (err) {
(*err) += "File write error: " + filepath + "\n";
}
return false;
}
f.close();
return true;
}
#endif // TINYGLTF_NO_FS
static std::string MimeToExt(const std::string &mimeType) { static std::string MimeToExt(const std::string &mimeType) {
if (mimeType == "image/jpeg") { if (mimeType == "image/jpeg") {
return "jpg"; return "jpg";
@ -1929,8 +2099,9 @@ static bool ParseAsset(Asset *asset, std::string *err, const json &o) {
static bool ParseImage(Image *image, std::string *err, const json &o, static bool ParseImage(Image *image, std::string *err, const json &o,
const std::string &basedir, const std::string &basedir,
FsCallbacks* fs,
LoadImageDataFunction *LoadImageData = nullptr, LoadImageDataFunction *LoadImageData = nullptr,
void *user_data = nullptr) { void *load_image_user_data = nullptr) {
// A glTF image must either reference a bufferView or an image uri // A glTF image must either reference a bufferView or an image uri
// schema says oneOf [`bufferView`, `uri`] // schema says oneOf [`bufferView`, `uri`]
@ -2012,7 +2183,7 @@ static bool ParseImage(Image *image, std::string *err, const json &o,
#ifdef TINYGLTF_NO_EXTERNAL_IMAGE #ifdef TINYGLTF_NO_EXTERNAL_IMAGE
return true; return true;
#endif #endif
if (!LoadExternalFile(&img, err, uri, basedir, 0, false)) { if (!LoadExternalFile(&img, err, uri, basedir, 0, false, fs)) {
if (err) { if (err) {
(*err) += "Failed to load external 'uri' for image parameter\n"; (*err) += "Failed to load external 'uri' for image parameter\n";
} }
@ -2035,7 +2206,7 @@ static bool ParseImage(Image *image, std::string *err, const json &o,
return false; return false;
} }
return (*LoadImageData)(image, err, 0, 0, &img.at(0), return (*LoadImageData)(image, err, 0, 0, &img.at(0),
static_cast<int>(img.size()), user_data); static_cast<int>(img.size()), load_image_user_data);
} }
static bool ParseTexture(Texture *texture, std::string *err, const json &o, static bool ParseTexture(Texture *texture, std::string *err, const json &o,
@ -2057,6 +2228,7 @@ static bool ParseTexture(Texture *texture, std::string *err, const json &o,
} }
static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
FsCallbacks* fs,
const std::string &basedir, bool is_binary = false, const std::string &basedir, bool is_binary = false,
const unsigned char *bin_data = nullptr, const unsigned char *bin_data = nullptr,
size_t bin_size = 0) { size_t bin_size = 0) {
@ -2092,7 +2264,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
if (!buffer->uri.empty()) { if (!buffer->uri.empty()) {
// External .bin file. // External .bin file.
LoadExternalFile(&buffer->data, err, buffer->uri, basedir, bytes, true); LoadExternalFile(&buffer->data, err, buffer->uri, basedir, bytes, true, fs);
} else { } else {
// load data from (embedded) binary data // load data from (embedded) binary data
@ -2131,7 +2303,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
} else { } else {
// Assume external .bin file. // Assume external .bin file.
if (!LoadExternalFile(&buffer->data, err, buffer->uri, basedir, bytes, if (!LoadExternalFile(&buffer->data, err, buffer->uri, basedir, bytes,
true)) { true, fs)) {
return false; return false;
} }
} }
@ -2925,7 +3097,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, const char *str,
return false; return false;
} }
Buffer buffer; Buffer buffer;
if (!ParseBuffer(&buffer, err, it->get<json>(), base_dir, is_binary_, if (!ParseBuffer(&buffer, err, it->get<json>(),
&fs, base_dir, is_binary_,
bin_data_, bin_size_)) { bin_data_, bin_size_)) {
return false; return false;
} }
@ -3127,7 +3300,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, const char *str,
return false; return false;
} }
Image image; Image image;
if (!ParseImage(&image, err, it.value(), base_dir, &this->LoadImageData, if (!ParseImage(&image, err, it.value(),
base_dir, &fs, &this->LoadImageData,
load_image_user_data_)) { load_image_user_data_)) {
return false; return false;
} }
@ -3356,19 +3530,28 @@ bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err,
unsigned int check_sections) { unsigned int check_sections) {
std::stringstream ss; std::stringstream ss;
std::ifstream f(filename.c_str()); if (fs.ReadWholeFile == nullptr) {
if (!f) { // Programmer error, assert() ?
ss << "Failed to open file: " << filename << std::endl; ss << "Failed to read file: " << filename <<
": one or more FS callback not set" << std::endl;
if (err) { if (err) {
(*err) = ss.str(); (*err) = ss.str();
} }
return false; return false;
} }
f.seekg(0, f.end); std::vector<unsigned char> data;
size_t sz = static_cast<size_t>(f.tellg()); std::string fileerr;
std::vector<char> buf(sz); bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data);
if(!fileread) {
ss << "Failed to read file: " << filename << ": " << fileerr << std::endl;
if (err) {
(*err) = ss.str();
}
return false;
}
size_t sz = data.size();
if (sz == 0) { if (sz == 0) {
if (err) { if (err) {
(*err) = "Empty file."; (*err) = "Empty file.";
@ -3376,14 +3559,12 @@ bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err,
return false; return false;
} }
f.seekg(0, f.beg);
f.read(&buf.at(0), static_cast<std::streamsize>(sz));
f.close();
std::string basedir = GetBaseDir(filename); std::string basedir = GetBaseDir(filename);
bool ret = LoadASCIIFromString(model, err, &buf.at(0), bool ret = LoadASCIIFromString(model, err,
static_cast<unsigned int>(buf.size()), basedir, reinterpret_cast<const char *>(&data.at(0)),
static_cast<unsigned int>(data.size()),
basedir,
check_sections); check_sections);
return ret; return ret;
@ -3462,28 +3643,32 @@ bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err,
unsigned int check_sections) { unsigned int check_sections) {
std::stringstream ss; std::stringstream ss;
std::ifstream f(filename.c_str(), std::ios::binary); if (fs.ReadWholeFile == nullptr) {
if (!f) { // Programmer error, assert() ?
ss << "Failed to open file: " << filename << std::endl; ss << "Failed to read file: " << filename <<
": one or more FS callback not set" << std::endl;
if (err) { if (err) {
(*err) = ss.str(); (*err) = ss.str();
} }
return false; return false;
} }
f.seekg(0, f.end); std::vector<unsigned char> data;
size_t sz = static_cast<size_t>(f.tellg()); std::string fileerr;
std::vector<char> buf(sz); bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data);
if(!fileread) {
f.seekg(0, f.beg); ss << "Failed to read file: " << filename << ": " << fileerr << std::endl;
f.read(&buf.at(0), static_cast<std::streamsize>(sz)); if (err) {
f.close(); (*err) = ss.str();
}
return false;
}
std::string basedir = GetBaseDir(filename); std::string basedir = GetBaseDir(filename);
bool ret = LoadBinaryFromMemory( bool ret = LoadBinaryFromMemory(
model, err, reinterpret_cast<unsigned char *>(&buf.at(0)), model, err, &data.at(0),
static_cast<unsigned int>(buf.size()), basedir, check_sections); static_cast<unsigned int>(data.size()), basedir, check_sections);
return ret; return ret;
} }