output both embedded and image files, small fix to serializer.

This commit is contained in:
johan bowald 2018-04-01 12:37:18 +02:00
parent 719d7e4a74
commit 642a343684
3 changed files with 206 additions and 32 deletions

View File

@ -73,11 +73,12 @@ TinyGLTF uses the following third party libraries.
* json.hpp : Copyright (c) 2013-2017 Niels Lohmann. MIT license. * json.hpp : Copyright (c) 2013-2017 Niels Lohmann. MIT license.
* base64 : Copyright (C) 2004-2008 René Nyffenegger * base64 : Copyright (C) 2004-2008 René Nyffenegger
* stb_image.h : v2.08 - public domain image loader - http://nothings.org/stb_image.h * stb_image.h : v2.08 - public domain image loader - http://nothings.org/stb_image.h
* stb_image_write.h : v1.09 - public domain image writer - http://nothings.org/stb_image_write.h
## Build and example ## Build and example
Copy `stb_image.h`, `json.hpp` and `tiny_gltf.h` to your project. Copy `stb_image.h`, `stb_image_write.h`, `json.hpp` and `tiny_gltf.h` to your project.
### Loading glTF 2.0 model ### Loading glTF 2.0 model
@ -110,10 +111,17 @@ if (!ret) {
* `TINYGLTF_NOEXCEPTION` : Disable C++ exception in JSON parsing. You can use `-fno-exceptions` or by defining the symbol `JSON_NOEXCEPTION` and `TINYGLTF_NOEXCEPTION` to fully remove C++ exception codes when compiling TinyGLTF. * `TINYGLTF_NOEXCEPTION` : Disable C++ exception in JSON parsing. You can use `-fno-exceptions` or by defining the symbol `JSON_NOEXCEPTION` and `TINYGLTF_NOEXCEPTION` to fully remove C++ exception codes when compiling TinyGLTF.
* `TINYGLTF_NO_STB_IMAGE` : Do not load images with stb_image. Instead use `TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)` to set a callback for loading images. * `TINYGLTF_NO_STB_IMAGE` : Do not load images with stb_image. Instead use `TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)` to set a callback for loading images.
* `TINYGLTF_NO_STB_IMAGE_WRITE` : Do not write images with stb_image_write. Instead use `TinyGLTF::SetImageWriter(WriteimageDataFunction WriteImageData, void *user_data)` to set a callback for writing images.
### Saving gltTF 2.0 model ### Saving gltTF 2.0 model
* [ ] Buffers.
T.B.W. * [x] To file
* [x] Embedded
* [ ] Draco compressed?
* [x] Images
* [x] To file
* [x] Embedded
* [ ] Binary(.glb)
## Running tests. ## Running tests.

View File

@ -3,11 +3,21 @@
#define TINYGLTF_IMPLEMENTATION #define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
//#define TINYGLTF_NO_STB_IMAGE_WRITE
#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
#define STB_IMAGE_WRITE_IMPLEMENTATION
#endif
// If using a modern Microsoft Compiler, this define supress compilation
// warnings in stb_image_write
//#define STBI_MSC_SECURE_CRT
#include "tiny_gltf.h" #include "tiny_gltf.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[]) {
{ if (argc != 3) {
if (argc != 3) {
std::cout << "Needs input.gltf output.gltf" << std::endl; std::cout << "Needs input.gltf output.gltf" << std::endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -17,7 +27,8 @@ int main(int argc, char *argv[])
std::string err; std::string err;
std::string input_filename(argv[1]); std::string input_filename(argv[1]);
std::string output_filename(argv[2]); std::string output_filename(argv[2]);
std::string embedded_filename = output_filename.substr(0, output_filename.size() - 5) + "-Embedded.gltf"; std::string embedded_filename =
output_filename.substr(0, output_filename.size() - 5) + "-Embedded.gltf";
// assume ascii glTF. // assume ascii glTF.
bool ret = loader.LoadASCIIFromFile(&model, &err, input_filename.c_str()); bool ret = loader.LoadASCIIFromFile(&model, &err, input_filename.c_str());
@ -29,9 +40,10 @@ int main(int argc, char *argv[])
} }
loader.WriteGltfSceneToFile(&model, output_filename); loader.WriteGltfSceneToFile(&model, output_filename);
// embed buffers // Embedd buffers and images
loader.WriteGltfSceneToFile(&model, embedded_filename, true); #ifndef TINYGLTF_NO_STB_IMAGE_WRITE
loader.WriteGltfSceneToFile(&model, embedded_filename, true, true);
#endif
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -730,6 +730,12 @@ enum SectionCheck {
typedef bool (*LoadImageDataFunction)(Image *, std::string *, int, int, typedef bool (*LoadImageDataFunction)(Image *, std::string *, int, int,
const unsigned char *, int, void *); const unsigned char *, int, void *);
///
/// WriteImageDataFunction type. Signature for custom image writing callbacks.
///
typedef bool (*WriteImageDataFunction)(const std::string *, const std::string *,
Image *, bool, void *);
#ifndef TINYGLTF_NO_STB_IMAGE #ifndef TINYGLTF_NO_STB_IMAGE
// Declaration of default image loader callback // Declaration of default image loader callback
bool LoadImageData(Image *image, std::string *err, int req_width, bool LoadImageData(Image *image, std::string *err, int req_width,
@ -737,6 +743,12 @@ bool LoadImageData(Image *image, std::string *err, int req_width,
void *); void *);
#endif #endif
#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
// Declaration of default image writer callback
bool WriteImageData(const std::string *basepath, const std::string *filename,
Image *image, bool embedImages, void *);
#endif
class TinyGLTF { class TinyGLTF {
public: public:
#ifdef __clang__ #ifdef __clang__
@ -792,15 +804,20 @@ class TinyGLTF {
/// ///
/// Write glTF to file. /// Write glTF to file.
/// ///
bool WriteGltfSceneToFile( bool WriteGltfSceneToFile(Model *model, const std::string &filename,
Model *model, const std::string &filename, bool embedImages,
bool embedBuffers /*, bool embedImages, bool writeBinary*/); bool embedBuffers /*, bool writeBinary*/);
/// ///
/// Set callback to use for loading image data /// Set callback to use for loading image data
/// ///
void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data); void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data);
///
/// Set callback to use for writing image data
///
void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data);
private: private:
/// ///
/// Loads glTF asset from string(memory). /// Loads glTF asset from string(memory).
@ -822,6 +839,14 @@ class TinyGLTF {
nullptr; nullptr;
#endif #endif
void *load_image_user_data_ = nullptr; void *load_image_user_data_ = nullptr;
WriteImageDataFunction WriteImageData =
#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
&tinygltf::WriteImageData;
#else
nullptr;
#endif
void *write_image_user_data_ = nullptr;
}; };
#ifdef __clang__ #ifdef __clang__
@ -873,6 +898,10 @@ class TinyGLTF {
#include "./stb_image.h" #include "./stb_image.h"
#endif #endif
#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
#include "./stb_image_write.h"
#endif
#ifdef __clang__ #ifdef __clang__
#pragma clang diagnostic pop #pragma clang diagnostic pop
#endif #endif
@ -1014,12 +1043,11 @@ static std::string FindFile(const std::vector<std::string> &paths,
return std::string(); return std::string();
} }
// std::string GetFilePathExtension(const std::string& FileName) std::string GetFilePathExtension(const std::string &FileName) {
//{ if (FileName.find_last_of(".") != std::string::npos)
// if(FileName.find_last_of(".") != std::string::npos) return FileName.substr(FileName.find_last_of(".") + 1);
// return FileName.substr(FileName.find_last_of(".")+1); return "";
// return ""; }
//}
static std::string GetBaseDir(const std::string &filepath) { static std::string GetBaseDir(const std::string &filepath) {
if (filepath.find_last_of("/\\") != std::string::npos) if (filepath.find_last_of("/\\") != std::string::npos)
@ -1294,6 +1322,121 @@ bool LoadImageData(Image *image, std::string *err, int req_width,
} }
#endif #endif
void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) {
WriteImageData = func;
write_image_user_data_ = user_data;
}
#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
static void WriteToMemory_stbi(void *context, void *data, int size) {
std::vector<unsigned char> *buffer =
reinterpret_cast<std::vector<unsigned char> *>(context);
unsigned char *pData = reinterpret_cast<unsigned char *>(data);
buffer->insert(buffer->end(), pData, pData + size);
}
bool WriteImageData(const std::string *basepath, const std::string *filename,
Image *image, bool embedImages, void *) {
const std::string ext = GetFilePathExtension(*filename);
if (embedImages) {
// Write image to memory and embed in output
std::string header;
std::vector<unsigned char> data;
if (ext == "png") {
stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width,
image->height, image->component, &image->image[0],
0);
header = "data:image/png;base64,";
} else if (ext == "jpg") {
stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width,
image->height, image->component, &image->image[0],
100);
header = "data:image/jpeg;base64,";
} else if (ext == "bmp") {
stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width,
image->height, image->component, &image->image[0]);
header = "data:image/bmp;base64,";
}
if (data.size()) {
image->uri =
header +
base64_encode(&data[0], static_cast<unsigned int>(data.size()));
} else {
// Throw error?
}
} else {
// Write image to disc
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]);
} else {
// Throw error? Cant output requested format.
}
image->uri = *filename;
}
return true;
}
#endif
static std::string MimeToExt(const std::string &mimeType) {
if (mimeType == "image/jpeg") {
return "jpg";
} else if (mimeType == "image/png") {
return "png";
} else if (mimeType == "image/bmp") {
return "bmp";
} else if (mimeType == "image/gif") {
return "gif";
}
return "";
}
void UpdateImageObject(Image &image, std::string &baseDir, int index,
bool embedImages,
WriteImageDataFunction *WriteImageData = nullptr,
void *user_data = nullptr) {
std::string filename;
std::string ext;
// If image have uri. Use it it as a filename
if (image.uri.size()) {
filename = GetBaseFilename(image.uri);
ext = GetFilePathExtension(filename);
} else if (image.name.size()) {
ext = MimeToExt(image.mimeType);
// Otherwise use name as filename
filename = image.name + "." + ext;
} else {
ext = MimeToExt(image.mimeType);
// Fallback to index of image as filename
filename = std::to_string(index) + "." + ext;
}
// If callback is set, modify image data object
if (*WriteImageData != nullptr) {
std::string uri;
(*WriteImageData)(&baseDir, &filename, &image, embedImages, user_data);
}
}
static bool IsDataURI(const std::string &in) { static bool IsDataURI(const std::string &in) {
std::string header = "data:application/octet-stream;base64,"; std::string header = "data:application/octet-stream;base64,";
if (in.find(header) == 0) { if (in.find(header) == 0) {
@ -1334,8 +1477,8 @@ static bool IsDataURI(const std::string &in) {
} }
static bool DecodeDataURI(std::vector<unsigned char> *out, static bool DecodeDataURI(std::vector<unsigned char> *out,
const std::string &in, size_t reqBytes, std::string &mime_type, const std::string &in,
bool checkSize) { size_t reqBytes, bool checkSize) {
std::string header = "data:application/octet-stream;base64,"; std::string header = "data:application/octet-stream;base64,";
std::string data; std::string data;
if (in.find(header) == 0) { if (in.find(header) == 0) {
@ -1345,6 +1488,7 @@ static bool DecodeDataURI(std::vector<unsigned char> *out,
if (data.empty()) { if (data.empty()) {
header = "data:image/jpeg;base64,"; header = "data:image/jpeg;base64,";
if (in.find(header) == 0) { if (in.find(header) == 0) {
mime_type = "image/jpeg";
data = base64_decode(in.substr(header.size())); // cut mime string. data = base64_decode(in.substr(header.size())); // cut mime string.
} }
} }
@ -1352,6 +1496,7 @@ static bool DecodeDataURI(std::vector<unsigned char> *out,
if (data.empty()) { if (data.empty()) {
header = "data:image/png;base64,"; header = "data:image/png;base64,";
if (in.find(header) == 0) { if (in.find(header) == 0) {
mime_type = "image/png";
data = base64_decode(in.substr(header.size())); // cut mime string. data = base64_decode(in.substr(header.size())); // cut mime string.
} }
} }
@ -1359,6 +1504,7 @@ static bool DecodeDataURI(std::vector<unsigned char> *out,
if (data.empty()) { if (data.empty()) {
header = "data:image/bmp;base64,"; header = "data:image/bmp;base64,";
if (in.find(header) == 0) { if (in.find(header) == 0) {
mime_type = "image/bmp";
data = base64_decode(in.substr(header.size())); // cut mime string. data = base64_decode(in.substr(header.size())); // cut mime string.
} }
} }
@ -1366,6 +1512,7 @@ static bool DecodeDataURI(std::vector<unsigned char> *out,
if (data.empty()) { if (data.empty()) {
header = "data:image/gif;base64,"; header = "data:image/gif;base64,";
if (in.find(header) == 0) { if (in.find(header) == 0) {
mime_type = "image/gif";
data = base64_decode(in.substr(header.size())); // cut mime string. data = base64_decode(in.substr(header.size())); // cut mime string.
} }
} }
@ -1373,6 +1520,7 @@ static bool DecodeDataURI(std::vector<unsigned char> *out,
if (data.empty()) { if (data.empty()) {
header = "data:text/plain;base64,"; header = "data:text/plain;base64,";
if (in.find(header) == 0) { if (in.find(header) == 0) {
mime_type = "text/plain";
data = base64_decode(in.substr(header.size())); data = base64_decode(in.substr(header.size()));
} }
} }
@ -1767,7 +1915,7 @@ static bool ParseImage(Image *image, std::string *err, const json &o,
std::vector<unsigned char> img; std::vector<unsigned char> img;
if (IsDataURI(uri)) { if (IsDataURI(uri)) {
if (!DecodeDataURI(&img, uri, 0, false)) { if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) {
if (err) { if (err) {
(*err) += "Failed to decode 'uri' for image parameter.\n"; (*err) += "Failed to decode 'uri' for image parameter.\n";
} }
@ -1886,7 +2034,8 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
} else { } else {
if (IsDataURI(buffer->uri)) { if (IsDataURI(buffer->uri)) {
if (!DecodeDataURI(&buffer->data, buffer->uri, bytes, true)) { if (!DecodeDataURI(&buffer->data, std::string(), buffer->uri, bytes,
true)) {
if (err) { if (err) {
(*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n";
} }
@ -3551,7 +3700,6 @@ static void SerializeGltfBufferView(BufferView &bufferView, json &o) {
} }
} }
// Only external textures are serialized for now
static void SerializeGltfImage(Image &image, json &o) { static void SerializeGltfImage(Image &image, json &o) {
SerializeStringProperty("uri", image.uri, o); SerializeStringProperty("uri", image.uri, o);
@ -3749,7 +3897,9 @@ static void SerializeGltfSkin(Skin &skin, json &o) {
} }
static void SerializeGltfTexture(Texture &texture, json &o) { static void SerializeGltfTexture(Texture &texture, json &o) {
SerializeNumberProperty("sampler", texture.sampler, o); if (texture.sampler > 0) {
SerializeNumberProperty("sampler", texture.sampler, o);
}
SerializeNumberProperty("source", texture.source, o); SerializeNumberProperty("source", texture.source, o);
if (texture.extras.Size()) { if (texture.extras.Size()) {
@ -3765,9 +3915,10 @@ static void WriteGltfFile(const std::string &output,
gltfFile << content << std::endl; gltfFile << content << std::endl;
} }
bool TinyGLTF::WriteGltfSceneToFile( bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename,
Model *model, const std::string &filename, bool embedImages = false,
bool embedBuffers = false /*, bool embedImages, bool writeBinary*/) { bool embedBuffers = false
/*, bool writeBinary*/) {
json output; json output;
// ACCESSORS // ACCESSORS
@ -3806,12 +3957,12 @@ bool TinyGLTF::WriteGltfSceneToFile(
} else { } else {
binFilename = binFilename + ".bin"; binFilename = binFilename + ".bin";
} }
std::string binSaveFilePath = GetBaseDir(filename); std::string baseDir = GetBaseDir(filename);
if (binSaveFilePath.empty()) { if (baseDir.empty()) {
binSaveFilePath = "./"; baseDir = "./";
} }
binSaveFilePath = JoinPath(binSaveFilePath, binFilename); std::string binSaveFilePath = JoinPath(baseDir, binFilename);
// BUFFERS (We expect only one buffer here) // BUFFERS (We expect only one buffer here)
json buffers; json buffers;
@ -3853,6 +4004,9 @@ bool TinyGLTF::WriteGltfSceneToFile(
json images; json images;
for (unsigned int i = 0; i < model->images.size(); ++i) { for (unsigned int i = 0; i < model->images.size(); ++i) {
json image; json image;
UpdateImageObject(model->images[i], baseDir, i, embedImages,
&this->WriteImageData, &this->write_image_user_data_);
SerializeGltfImage(model->images[i], image); SerializeGltfImage(model->images[i], image);
images.push_back(image); images.push_back(image);
} }