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 *);
#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 {
public:
#ifdef __clang__
@ -823,6 +884,11 @@ class TinyGLTF {
///
void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data);
///
/// Set callbacks to use for filesystem (fs) access and their user data
///
void SetFsCallbacks(FsCallbacks callbacks);
private:
///
/// Loads glTF asset from string(memory).
@ -837,13 +903,31 @@ class TinyGLTF {
size_t bin_size_;
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 =
#ifndef TINYGLTF_NO_STB_IMAGE
&tinygltf::LoadImageData;
#else
nullptr;
#endif
void *load_image_user_data_ = nullptr;
void *load_image_user_data_ = reinterpret_cast<void *>(&fs);
WriteImageDataFunction WriteImageData =
#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
@ -851,7 +935,8 @@ class TinyGLTF {
#else
nullptr;
#endif
void *write_image_user_data_ = nullptr;
void *write_image_user_data_ = reinterpret_cast<void *>(&fs);
};
#ifdef __clang__
@ -865,7 +950,9 @@ class TinyGLTF {
#ifdef TINYGLTF_IMPLEMENTATION
#include <algorithm>
//#include <cassert>
#ifndef TINYGLTF_NO_FS
#include <fstream>
#endif
#include <sstream>
#ifdef __clang__
@ -966,74 +1053,6 @@ static void swap4(unsigned int *val) {
#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,
const std::string &path1) {
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,
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++) {
std::string absPath = ExpandFilePath(JoinPath(paths[i], filepath));
if (FileExists(absPath)) {
std::string absPath = fs->ExpandFilePath(JoinPath(paths[i], filepath),
fs->user_data);
if (fs->FileExists(absPath, fs->user_data)) {
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,
const std::string &filename,
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();
std::vector<std::string> paths;
paths.push_back(basedir);
paths.push_back(".");
std::string filepath = FindFile(paths, filename);
std::string filepath = FindFile(paths, filename, fs);
if (filepath.empty() || filename.empty()) {
if (err) {
(*err) += "File not found : " + filename + "\n";
@ -1231,31 +1272,22 @@ static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
return false;
}
std::ifstream f(filepath.c_str(), std::ifstream::binary);
if (!f) {
std::vector<unsigned char> buf;
std::string fileReadErr;
bool fileRead = fs->ReadWholeFile(&buf, &fileReadErr, filepath,
fs->user_data);
if (!fileRead) {
if (err) {
(*err) += "File open error : " + filepath + "\n";
(*err) += "File read error : " + filepath + " : " + fileReadErr + "\n";
}
return false;
}
f.seekg(0, f.end);
size_t sz = static_cast<size_t>(f.tellg());
if (int(sz) < 0) {
// Looks reading directory, not a file.
return false;
}
size_t sz = buf.size();
if (sz == 0) {
// Invalid file size.
(*err) += "File is empty : " + filepath + "\n";
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 (reqBytes == sz) {
@ -1356,11 +1388,10 @@ static void WriteToMemory_stbi(void *context, void *data, int size) {
}
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);
if (embedImages) {
// Write image to memory and embed in output
// Write image to temporary buffer
std::string header;
std::vector<unsigned char> data;
@ -1378,8 +1409,13 @@ bool WriteImageData(const std::string *basepath, const std::string *filename,
stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width,
image->height, image->component, &image->image[0]);
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()) {
image->uri =
header +
@ -1389,21 +1425,16 @@ bool WriteImageData(const std::string *basepath, const std::string *filename,
}
} else {
// Write image to disc
FsCallbacks *fs = reinterpret_cast<FsCallbacks *>(fsPtr);
if (fs != nullptr && fs->WriteWholeFile == nullptr)
{
const std::string imagefilepath = JoinPath(*basepath, *filename);
if (ext == "png") {
stbi_write_png(imagefilepath.c_str(), image->width, image->height,
image->component, &image->image[0], 0);
} else if (ext == "jpg") {
// TODO (Bowald): Give user the option to set output quality?
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]);
std::string writeError;
if(!fs->WriteWholeFile(&writeError, imagefilepath, data, fs->user_data)) {
// Could not write image file to disc; Throw error ?
}
} else {
// Throw error? Cant output requested format.
// Throw error?
}
image->uri = *filename;
}
@ -1412,6 +1443,145 @@ bool WriteImageData(const std::string *basepath, const std::string *filename,
}
#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) {
if (mimeType == "image/jpeg") {
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,
const std::string &basedir,
FsCallbacks* fs,
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
// 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
return true;
#endif
if (!LoadExternalFile(&img, err, uri, basedir, 0, false)) {
if (!LoadExternalFile(&img, err, uri, basedir, 0, false, fs)) {
if (err) {
(*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 (*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,
@ -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,
FsCallbacks* fs,
const std::string &basedir, bool is_binary = false,
const unsigned char *bin_data = nullptr,
size_t bin_size = 0) {
@ -2092,7 +2264,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
if (!buffer->uri.empty()) {
// External .bin file.
LoadExternalFile(&buffer->data, err, buffer->uri, basedir, bytes, true);
LoadExternalFile(&buffer->data, err, buffer->uri, basedir, bytes, true, fs);
} else {
// load data from (embedded) binary data
@ -2131,7 +2303,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
} else {
// Assume external .bin file.
if (!LoadExternalFile(&buffer->data, err, buffer->uri, basedir, bytes,
true)) {
true, fs)) {
return false;
}
}
@ -2925,7 +3097,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, const char *str,
return false;
}
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_)) {
return false;
}
@ -3127,7 +3300,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, const char *str,
return false;
}
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_)) {
return false;
}
@ -3356,19 +3530,28 @@ bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err,
unsigned int check_sections) {
std::stringstream ss;
std::ifstream f(filename.c_str());
if (!f) {
ss << "Failed to open file: " << filename << std::endl;
if (fs.ReadWholeFile == nullptr) {
// Programmer error, assert() ?
ss << "Failed to read file: " << filename <<
": one or more FS callback not set" << std::endl;
if (err) {
(*err) = ss.str();
}
return false;
}
f.seekg(0, f.end);
size_t sz = static_cast<size_t>(f.tellg());
std::vector<char> buf(sz);
std::vector<unsigned char> data;
std::string fileerr;
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 (err) {
(*err) = "Empty file.";
@ -3376,14 +3559,12 @@ bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err,
return false;
}
f.seekg(0, f.beg);
f.read(&buf.at(0), static_cast<std::streamsize>(sz));
f.close();
std::string basedir = GetBaseDir(filename);
bool ret = LoadASCIIFromString(model, err, &buf.at(0),
static_cast<unsigned int>(buf.size()), basedir,
bool ret = LoadASCIIFromString(model, err,
reinterpret_cast<const char *>(&data.at(0)),
static_cast<unsigned int>(data.size()),
basedir,
check_sections);
return ret;
@ -3462,28 +3643,32 @@ bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err,
unsigned int check_sections) {
std::stringstream ss;
std::ifstream f(filename.c_str(), std::ios::binary);
if (!f) {
ss << "Failed to open file: " << filename << std::endl;
if (fs.ReadWholeFile == nullptr) {
// Programmer error, assert() ?
ss << "Failed to read file: " << filename <<
": one or more FS callback not set" << std::endl;
if (err) {
(*err) = ss.str();
}
return false;
}
f.seekg(0, f.end);
size_t sz = static_cast<size_t>(f.tellg());
std::vector<char> buf(sz);
f.seekg(0, f.beg);
f.read(&buf.at(0), static_cast<std::streamsize>(sz));
f.close();
std::vector<unsigned char> data;
std::string fileerr;
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;
}
std::string basedir = GetBaseDir(filename);
bool ret = LoadBinaryFromMemory(
model, err, reinterpret_cast<unsigned char *>(&buf.at(0)),
static_cast<unsigned int>(buf.size()), basedir, check_sections);
model, err, &data.at(0),
static_cast<unsigned int>(data.size()), basedir, check_sections);
return ret;
}