New Upstream Release - tinygltf

Ready changes

Summary

Merged new upstream version: 2.8.2+dfsg (was: 2.7.0+dfsg).

Diff

diff --git a/README.md b/README.md
index b3c2ed8..bb88fb5 100644
--- a/README.md
+++ b/README.md
@@ -2,20 +2,22 @@
 
 `TinyGLTF` is a header only C++11 glTF 2.0 https://github.com/KhronosGroup/glTF library.
 
-`TinyGLTF` uses Niels Lohmann's json library(https://github.com/nlohmann/json), so now it requires C++11 compiler.
+`TinyGLTF` uses Niels Lohmann's json library (https://github.com/nlohmann/json), so now it requires C++11 compiler.
 (Also, you can use RadpidJSON as an JSON backend)
-If you are looking for old, C++03 version, please use `devel-picojson` branch(but not maintained anymore).
+If you are looking for old, C++03 version, please use `devel-picojson` branch (but not maintained anymore).
 
 ## Status
 
-Currently TinyGLTF is stable and maintainance mode. No drastic changes and feature additions planned.
+Currently TinyGLTF is stable and maintenance mode. No drastic changes and feature additions planned.
 
+ - v2.8.0 Add URICallbacks for custom URI handling in Buffer and Image. PR#397
+ - v2.7.0 Change WriteImageDataFunction user callback function signature. PR#393
  - v2.6.0 Support serializing sparse accessor(Thanks to @fynv).
  - v2.5.0 Add SetPreserveImageChannels() option to load image data as is.
  - v2.4.0 Experimental RapidJSON support. Experimental C++14 support(C++14 may give better performance)
  - v2.3.0 Modified Material representation according to glTF 2.0 schema(and introduced TextureInfo class)
  - v2.2.0 release(Support loading 16bit PNG. Sparse accessor support)
- - v2.1.0 release(Draco support)
+ - v2.1.0 release(Draco decoding support)
  - v2.0.0 release(22 Aug, 2018)!
 
 ### Branches
diff --git a/debian/changelog b/debian/changelog
index a2d29fc..99600b5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+tinygltf (2.8.2+dfsg-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 08 Feb 2023 00:00:47 -0000
+
 tinygltf (2.7.0+dfsg-2) unstable; urgency=medium
 
   * Upload to unstable.
diff --git a/debian/patches/0001-Fix-build-system.patch b/debian/patches/0001-Fix-build-system.patch
index 81526e1..fecc3f8 100644
--- a/debian/patches/0001-Fix-build-system.patch
+++ b/debian/patches/0001-Fix-build-system.patch
@@ -10,10 +10,10 @@ Forwarded: not-needed
  3 files changed, 53 insertions(+), 46 deletions(-)
  create mode 100644 tiny_gltf.cpp
 
-diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 0df1614..4494276 100644
---- a/CMakeLists.txt
-+++ b/CMakeLists.txt
+Index: tinygltf.git/CMakeLists.txt
+===================================================================
+--- tinygltf.git.orig/CMakeLists.txt
++++ tinygltf.git/CMakeLists.txt
 @@ -1,11 +1,13 @@
  cmake_minimum_required(VERSION 3.6)
  
@@ -37,41 +37,13 @@ index 0df1614..4494276 100644
 -if (TINYGLTF_BUILD_BUILDER_EXAMPLE)
 -  ADD_SUBDIRECTORY ( examples/build-gltf )
 -endif (TINYGLTF_BUILD_BUILDER_EXAMPLE)
-+add_library(${PROJECT_NAME} tiny_gltf.h tiny_gltf.cpp)
-+target_include_directories(${PROJECT_NAME}
-+	PUBLIC
-+		$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
-+		$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
-+	PRIVATE
-+		${stb_INCLUDE_DIRS}
-+)
-+target_link_libraries(${PROJECT_NAME} PRIVATE
-+	${stb_LIBRARY}
-+	nlohmann_json::nlohmann_json
-+)
-+set_target_properties(${PROJECT_NAME} PROPERTIES
-+	OUTPUT_NAME "tinygltf"
-+	VERSION ${PROJECT_VERSION}
-+	SOVERSION 2d
-+)
-+add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
- 
+-
 -#
 -# for add_subdirectory and standalone build
 -#
 -if (TINYGLTF_HEADER_ONLY)
 -  add_library(tinygltf INTERFACE)
-+install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets
-+	RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
-+	ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
-+	LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
-+)
-+export(EXPORT ${PROJECT_NAME}Targets NAMESPACE ${PROJECT_NAME}::)
-+install(EXPORT ${PROJECT_NAME}Targets NAMESPACE ${PROJECT_NAME}::
-+	DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
-+)
-+install(FILES tiny_gltf.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
- 
+-
 -  target_include_directories(tinygltf
 -          INTERFACE
 -          $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
@@ -107,6 +79,36 @@ index 0df1614..4494276 100644
 -    )
 -
 -endif(TINYGLTF_INSTALL)
++add_library(${PROJECT_NAME} tiny_gltf.h tiny_gltf.cpp)
++target_include_directories(${PROJECT_NAME}
++	PUBLIC
++		$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
++		$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
++	PRIVATE
++		${stb_INCLUDE_DIRS}
++)
++target_link_libraries(${PROJECT_NAME} PRIVATE
++	${stb_LIBRARY}
++	nlohmann_json::nlohmann_json
++)
++set_target_properties(${PROJECT_NAME} PROPERTIES
++	OUTPUT_NAME "tinygltf"
++	VERSION ${PROJECT_VERSION}
++	SOVERSION 2d
++)
++add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
++
++install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets
++	RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
++	ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
++	LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
++)
++export(EXPORT ${PROJECT_NAME}Targets NAMESPACE ${PROJECT_NAME}::)
++install(EXPORT ${PROJECT_NAME}Targets NAMESPACE ${PROJECT_NAME}::
++	DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
++)
++install(FILES tiny_gltf.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
++
 +configure_package_config_file(cmake/${PROJECT_NAME}Config.cmake.in ${PROJECT_NAME}Config.cmake
 +	INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
 +        NO_CHECK_REQUIRED_COMPONENTS_MACRO
@@ -118,10 +120,10 @@ index 0df1614..4494276 100644
 +	${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
 +	DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
 +)
-diff --git a/cmake/TinyGLTFConfig.cmake.in b/cmake/TinyGLTFConfig.cmake.in
-index fcccacf..89f21e4 100644
---- a/cmake/TinyGLTFConfig.cmake.in
-+++ b/cmake/TinyGLTFConfig.cmake.in
+Index: tinygltf.git/cmake/TinyGLTFConfig.cmake.in
+===================================================================
+--- tinygltf.git.orig/cmake/TinyGLTFConfig.cmake.in
++++ tinygltf.git/cmake/TinyGLTFConfig.cmake.in
 @@ -1,3 +1,9 @@
  @PACKAGE_INIT@
  
@@ -133,11 +135,10 @@ index fcccacf..89f21e4 100644
 +set(@PROJECT_NAME@_CONFIG ${CMAKE_CURRENT_LIST_FILE})
 +find_package_handle_standard_args(@PROJECT_NAME@ CONFIG_MODE)
 +
-diff --git a/tiny_gltf.cpp b/tiny_gltf.cpp
-new file mode 100644
-index 0000000..43e41f4
+Index: tinygltf.git/tiny_gltf.cpp
+===================================================================
 --- /dev/null
-+++ b/tiny_gltf.cpp
++++ tinygltf.git/tiny_gltf.cpp
 @@ -0,0 +1,3 @@
 +#define TINYGLTF_IMPLEMENTATION
 +#include "tiny_gltf.h"
diff --git a/debian/patches/0002-Fix-third-party-includes.patch b/debian/patches/0002-Fix-third-party-includes.patch
index 52a3b21..b513af2 100644
--- a/debian/patches/0002-Fix-third-party-includes.patch
+++ b/debian/patches/0002-Fix-third-party-includes.patch
@@ -8,10 +8,10 @@ Forwarded: not-needed
  tiny_gltf.h     | 6 +++---
  2 files changed, 4 insertions(+), 4 deletions(-)
 
-diff --git a/tests/tester.cc b/tests/tester.cc
-index b43cf4c..f40645a 100644
---- a/tests/tester.cc
-+++ b/tests/tester.cc
+Index: tinygltf.git/tests/tester.cc
+===================================================================
+--- tinygltf.git.orig/tests/tester.cc
++++ tinygltf.git/tests/tester.cc
 @@ -4,7 +4,7 @@
  #include "tiny_gltf.h"
  
@@ -21,11 +21,11 @@ index b43cf4c..f40645a 100644
  
  #define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() - only do this in one cpp file
  #include "catch.hpp"
-diff --git a/tiny_gltf.h b/tiny_gltf.h
-index b83daa1..b22c6af 100644
---- a/tiny_gltf.h
-+++ b/tiny_gltf.h
-@@ -1568,7 +1568,7 @@ class TinyGLTF {
+Index: tinygltf.git/tiny_gltf.h
+===================================================================
+--- tinygltf.git.orig/tiny_gltf.h
++++ tinygltf.git/tiny_gltf.h
+@@ -1620,7 +1620,7 @@ class TinyGLTF {
  
  #ifndef TINYGLTF_NO_INCLUDE_JSON
  #ifndef TINYGLTF_USE_RAPIDJSON
@@ -34,7 +34,7 @@ index b83daa1..b22c6af 100644
  #else
  #ifndef TINYGLTF_NO_INCLUDE_RAPIDJSON
  #include "document.h"
-@@ -1587,13 +1587,13 @@ class TinyGLTF {
+@@ -1639,13 +1639,13 @@ class TinyGLTF {
  
  #ifndef TINYGLTF_NO_STB_IMAGE
  #ifndef TINYGLTF_NO_INCLUDE_STB_IMAGE
diff --git a/debian/patches/0003-Integrate-unit-tests-into-CMake-build.patch b/debian/patches/0003-Integrate-unit-tests-into-CMake-build.patch
index d14bdec..120a7c4 100644
--- a/debian/patches/0003-Integrate-unit-tests-into-CMake-build.patch
+++ b/debian/patches/0003-Integrate-unit-tests-into-CMake-build.patch
@@ -10,11 +10,11 @@ Forwarded: not-needed
  3 files changed, 15 insertions(+), 2 deletions(-)
  create mode 100644 tests/CMakeLists.txt
 
-diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 4494276..b0ea08d 100644
---- a/CMakeLists.txt
-+++ b/CMakeLists.txt
-@@ -4,6 +4,7 @@ project(TinyGLTF VERSION 2.7.0 LANGUAGES CXX)
+Index: tinygltf.git/CMakeLists.txt
+===================================================================
+--- tinygltf.git.orig/CMakeLists.txt
++++ tinygltf.git/CMakeLists.txt
+@@ -4,6 +4,7 @@ project(TinyGLTF VERSION 2.7.0 LANGUAGES
  
  include(GNUInstallDirs)
  include(CMakePackageConfigHelpers)
@@ -29,11 +29,10 @@ index 4494276..b0ea08d 100644
 +
 +add_subdirectory(tests)
 +
-diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
-new file mode 100644
-index 0000000..80148fc
+Index: tinygltf.git/tests/CMakeLists.txt
+===================================================================
 --- /dev/null
-+++ b/tests/CMakeLists.txt
++++ tinygltf.git/tests/CMakeLists.txt
 @@ -0,0 +1,9 @@
 +if(BUILD_TESTING)
 +	add_executable(tester tester.cc)
@@ -44,10 +43,10 @@ index 0000000..80148fc
 +	)
 +endif()
 +
-diff --git a/tests/tester.cc b/tests/tester.cc
-index f40645a..5edb0af 100644
---- a/tests/tester.cc
-+++ b/tests/tester.cc
+Index: tinygltf.git/tests/tester.cc
+===================================================================
+--- tinygltf.git.orig/tests/tester.cc
++++ tinygltf.git/tests/tester.cc
 @@ -1,13 +1,13 @@
  #define TINYGLTF_IMPLEMENTATION
  #define STB_IMAGE_IMPLEMENTATION
diff --git a/debian/patches/0004-Add-pkgconfig-file.patch b/debian/patches/0004-Add-pkgconfig-file.patch
index d57da3e..b5f7808 100644
--- a/debian/patches/0004-Add-pkgconfig-file.patch
+++ b/debian/patches/0004-Add-pkgconfig-file.patch
@@ -9,10 +9,10 @@ Forwarded: not-needed
  2 files changed, 13 insertions(+)
  create mode 100644 tinygltf.pc.in
 
-diff --git a/CMakeLists.txt b/CMakeLists.txt
-index b0ea08d..7fa99c1 100644
---- a/CMakeLists.txt
-+++ b/CMakeLists.txt
+Index: tinygltf.git/CMakeLists.txt
+===================================================================
+--- tinygltf.git.orig/CMakeLists.txt
++++ tinygltf.git/CMakeLists.txt
 @@ -74,5 +74,8 @@ install(FILES
  	DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
  )
@@ -22,11 +22,10 @@ index b0ea08d..7fa99c1 100644
 +
  add_subdirectory(tests)
  
-diff --git a/tinygltf.pc.in b/tinygltf.pc.in
-new file mode 100644
-index 0000000..8e7bae2
+Index: tinygltf.git/tinygltf.pc.in
+===================================================================
 --- /dev/null
-+++ b/tinygltf.pc.in
++++ tinygltf.git/tinygltf.pc.in
 @@ -0,0 +1,10 @@
 +prefix=@CMAKE_INSTALL_PREFIX@
 +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
diff --git a/debian/patches/0005-Hide-nlohmann_json-symbols.patch b/debian/patches/0005-Hide-nlohmann_json-symbols.patch
index b2c71bb..2a83712 100644
--- a/debian/patches/0005-Hide-nlohmann_json-symbols.patch
+++ b/debian/patches/0005-Hide-nlohmann_json-symbols.patch
@@ -6,11 +6,11 @@ Subject: Hide nlohmann_json symbols
  tiny_gltf.h | 6 ++++++
  1 file changed, 6 insertions(+)
 
-diff --git a/tiny_gltf.h b/tiny_gltf.h
-index b22c6af..15f63c4 100644
---- a/tiny_gltf.h
-+++ b/tiny_gltf.h
-@@ -1568,7 +1568,13 @@ class TinyGLTF {
+Index: tinygltf.git/tiny_gltf.h
+===================================================================
+--- tinygltf.git.orig/tiny_gltf.h
++++ tinygltf.git/tiny_gltf.h
+@@ -1620,7 +1620,13 @@ class TinyGLTF {
  
  #ifndef TINYGLTF_NO_INCLUDE_JSON
  #ifndef TINYGLTF_USE_RAPIDJSON
diff --git a/debian/patches/0006-Big-endian-fix.patch b/debian/patches/0006-Big-endian-fix.patch
index 6c7be84..fa27b5d 100644
--- a/debian/patches/0006-Big-endian-fix.patch
+++ b/debian/patches/0006-Big-endian-fix.patch
@@ -6,11 +6,11 @@ Subject: Big endian fix
  tiny_gltf.h | 24 +++++++++++++-----------
  1 file changed, 13 insertions(+), 11 deletions(-)
 
-diff --git a/tiny_gltf.h b/tiny_gltf.h
-index 15f63c4..1cc8e36 100644
---- a/tiny_gltf.h
-+++ b/tiny_gltf.h
-@@ -1651,13 +1651,9 @@ class TinyGLTF {
+Index: tinygltf.git/tiny_gltf.h
+===================================================================
+--- tinygltf.git.orig/tiny_gltf.h
++++ tinygltf.git/tiny_gltf.h
+@@ -1703,13 +1703,9 @@ class TinyGLTF {
  //#include <wordexp.h>
  #endif
  
@@ -25,7 +25,7 @@ index 15f63c4..1cc8e36 100644
  
  namespace {
  #ifdef TINYGLTF_USE_RAPIDJSON
-@@ -7708,7 +7704,8 @@ static bool WriteBinaryGltfStream(std::ostream &stream,
+@@ -7814,7 +7810,8 @@ static bool WriteBinaryGltfStream(std::o
                                    const std::string &content,
                                    const std::vector<unsigned char> &binBuffer) {
    const std::string header = "glTF";
@@ -35,7 +35,7 @@ index 15f63c4..1cc8e36 100644
  
    const uint32_t content_size = uint32_t(content.size());
    const uint32_t binBuffer_size = uint32_t(binBuffer.size());
-@@ -7720,17 +7717,20 @@ static bool WriteBinaryGltfStream(std::ostream &stream,
+@@ -7826,17 +7823,20 @@ static bool WriteBinaryGltfStream(std::o
  
    // 12 bytes for header, JSON content length, 8 bytes for JSON chunk info.
    // Chunk data must be located at 4-byte boundary, which may require padding
@@ -59,7 +59,7 @@ index 15f63c4..1cc8e36 100644
    stream.write(reinterpret_cast<const char *>(&model_length),
                 sizeof(model_length));
    stream.write(reinterpret_cast<const char *>(&model_format),
-@@ -7744,8 +7744,10 @@ static bool WriteBinaryGltfStream(std::ostream &stream,
+@@ -7850,8 +7850,10 @@ static bool WriteBinaryGltfStream(std::o
    }
    if (binBuffer.size() > 0) {
      // BIN chunk info, then BIN data
diff --git a/tests/tester.cc b/tests/tester.cc
index b43cf4c..b1be601 100644
--- a/tests/tester.cc
+++ b/tests/tester.cc
@@ -392,27 +392,105 @@ TEST_CASE("pbr-khr-texture-transform", "[material]") {
 
 TEST_CASE("image-uri-spaces", "[issue-236]") {
 
-  tinygltf::Model model;
   tinygltf::TinyGLTF ctx;
   std::string err;
   std::string warn;
 
   // Test image file with single spaces.
-  bool ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/CubeImageUriSpaces/CubeImageUriSpaces.gltf");
-  if (!err.empty()) {
-    std::cerr << err << std::endl;
-  }
+  {
+    tinygltf::Model model;
+    bool ret = ctx.LoadASCIIFromFile(
+        &model, &err, &warn,
+        "../models/CubeImageUriSpaces/CubeImageUriSpaces.gltf");
+    if (!warn.empty()) {
+      std::cerr << warn << std::endl;
+    }
+    if (!err.empty()) {
+      std::cerr << err << std::endl;
+    }
 
-  REQUIRE(true == ret);
+    REQUIRE(true == ret);
+    REQUIRE(warn.empty());
+    REQUIRE(err.empty());
+    REQUIRE(model.images.size() == 1);
+    REQUIRE(model.images[0].uri.find(' ') != std::string::npos);
+  }
 
   // Test image file with a beginning space, trailing space, and greater than
   // one consecutive spaces.
-  ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/CubeImageUriSpaces/CubeImageUriMultipleSpaces.gltf");
+  tinygltf::Model model;
+  bool ret = ctx.LoadASCIIFromFile(
+      &model, &err, &warn,
+      "../models/CubeImageUriSpaces/CubeImageUriMultipleSpaces.gltf");
+  if (!warn.empty()) {
+    std::cerr << warn << std::endl;
+  }
   if (!err.empty()) {
     std::cerr << err << std::endl;
   }
 
   REQUIRE(true == ret);
+  REQUIRE(warn.empty());
+  REQUIRE(err.empty());
+  REQUIRE(model.images.size() == 1);
+  REQUIRE(model.images[0].uri.size() > 1);
+  REQUIRE(model.images[0].uri[0] == ' ');
+
+  // Test the URI encoding API by saving and re-load the file, without embedding
+  // the image.
+  // TODO(syoyo): create temp directory.
+  {
+    // Encoder that only replaces spaces with "%20".
+    auto uriencode = [](const std::string &in_uri,
+                        const std::string &object_type, std::string *out_uri,
+                        void *user_data) -> bool {
+      (void)user_data;
+      bool imageOrBuffer = object_type == "image" || object_type == "buffer";
+      REQUIRE(true == imageOrBuffer);
+      *out_uri = {};
+      for (char c : in_uri) {
+        if (c == ' ')
+          *out_uri += "%20";
+        else
+          *out_uri += c;
+      }
+      return true;
+    };
+
+    // Remove the buffer URI, so a new one is generated based on the gltf
+    // filename and then encoded with the above callback.
+    model.buffers[0].uri.clear();
+
+    tinygltf::URICallbacks uri_cb{uriencode, tinygltf::URIDecode, nullptr};
+    ctx.SetURICallbacks(uri_cb);
+    ret = ctx.WriteGltfSceneToFile(&model, " issue-236.gltf", false, false);
+    REQUIRE(true == ret);
+
+    // read back serialized glTF
+    tinygltf::Model saved;
+    bool ret = ctx.LoadASCIIFromFile(&saved, &err, &warn, " issue-236.gltf");
+    if (!err.empty()) {
+      std::cerr << err << std::endl;
+    }
+    REQUIRE(true == ret);
+    REQUIRE(err.empty());
+    REQUIRE(!warn.empty());  // relative image path won't exist in tests/
+    REQUIRE(saved.images.size() == model.images.size());
+
+    // The image uri in CubeImageUriMultipleSpaces.gltf is not encoded and
+    // should be different after encoding spaces with %20.
+    REQUIRE(model.images[0].uri != saved.images[0].uri);
+
+    // Verify the image path remains the same after uri decoding
+    std::string image_uri, image_uri_saved;
+    (void)tinygltf::URIDecode(model.images[0].uri, &image_uri, nullptr);
+    (void)tinygltf::URIDecode(saved.images[0].uri, &image_uri_saved, nullptr);
+    REQUIRE(image_uri == image_uri_saved);
+
+    // Verify the buffer's generated and encoded URI
+    REQUIRE(saved.buffers.size() == model.buffers.size());
+    REQUIRE(saved.buffers[0].uri == "%20issue-236.bin");
+  }
 }
 
 TEST_CASE("serialize-empty-material", "[issue-294]") {
@@ -583,7 +661,11 @@ TEST_CASE("serialize-image-callback", "[issue-394]") {
 
   auto writer = [](const std::string *basepath, const std::string *filename,
                    const tinygltf::Image *image, bool embedImages,
-                   std::string *out_uri, void *user_pointer) -> bool {
+                   const tinygltf::URICallbacks *uri_cb, std::string *out_uri,
+                   void *user_pointer) -> bool {
+    (void)basepath;
+    (void)image;
+    (void)uri_cb;
     REQUIRE(*filename == "foo");
     REQUIRE(embedImages == true);
     REQUIRE(user_pointer == (void *)0xba5e1e55);
@@ -593,13 +675,46 @@ TEST_CASE("serialize-image-callback", "[issue-394]") {
 
   tinygltf::TinyGLTF ctx;
   ctx.SetImageWriter(writer, (void *)0xba5e1e55);
-  ctx.WriteGltfSceneToStream(const_cast<const tinygltf::Model *>(&m), os, false,
+  bool result = ctx.WriteGltfSceneToStream(const_cast<const tinygltf::Model *>(&m), os, false,
                              false);
 
   // use nlohmann json
   nlohmann::json j = nlohmann::json::parse(os.str());
 
+  REQUIRE(true == result);
   REQUIRE(1 == j["images"].size());
   REQUIRE(j["images"][0].is_object());
   REQUIRE(j["images"][0]["uri"].get<std::string>() == "bar");
 }
+
+TEST_CASE("serialize-image-failure", "[issue-394]") {
+  tinygltf::Model m;
+  tinygltf::Image i;
+  // Set some data so the ImageWriter callback will be called
+  i.image = {255, 255, 255, 255};
+  m.images.push_back(i);
+
+  std::stringstream os;
+
+  auto writer = [](const std::string *basepath, const std::string *filename,
+                   const tinygltf::Image *image, bool embedImages,
+                   const tinygltf::URICallbacks *uri_cb, std::string *out_uri,
+                   void *user_pointer) -> bool {
+    (void)basepath;
+    (void)filename;
+    (void)image;
+    (void)embedImages;
+    (void)uri_cb;
+    (void)out_uri;
+    (void)user_pointer;
+    return false;
+  };
+
+  tinygltf::TinyGLTF ctx;
+  ctx.SetImageWriter(writer, (void *)0xba5e1e55);
+  bool result = ctx.WriteGltfSceneToStream(const_cast<const tinygltf::Model *>(&m), os, false,
+                             false);
+
+  REQUIRE(false == result);
+  REQUIRE(os.str().size() == 0);
+}
diff --git a/tiny_gltf.h b/tiny_gltf.h
index b83daa1..b0e2c60 100644
--- a/tiny_gltf.h
+++ b/tiny_gltf.h
@@ -26,6 +26,8 @@
 // THE SOFTWARE.
 
 // Version:
+//  - v2.8.1 Missed serialization texture sampler name fixed. PR#399.
+//  - v2.8.0 Add URICallbacks for custom URI handling in Buffer and Image. PR#397.
 //  - v2.7.0 Change WriteImageDataFunction user callback function signature. PR#393.
 //  - v2.6.3 Fix GLB file with empty BIN chunk was not handled. PR#382 and PR#383.
 //  - v2.6.2 Fix out-of-bounds access of accessors. PR#379.
@@ -545,9 +547,10 @@ typedef std::map<std::string, Value> ExtensionMap;
 
 struct AnimationChannel {
   int sampler;              // required
-  int target_node;          // required (index of the node to target)
-  std::string target_path;  // required in ["translation", "rotation", "scale",
-                            // "weights"]
+  int target_node;          // optional index of the node to target (alternative
+                            // target should be provided by extension)
+  std::string target_path;  // required with standard values of ["translation",
+                            // "rotation", "scale", "weights"]
   Value extras;
   ExtensionMap extensions;
   ExtensionMap target_extensions;
@@ -1207,6 +1210,39 @@ enum SectionCheck {
   REQUIRE_ALL = 0x7f
 };
 
+///
+/// URIEncodeFunction type. Signature for custom URI encoding of external
+/// resources such as .bin and image files. Used by tinygltf to re-encode the
+/// final location of saved files. object_type may be used to encode buffer and
+/// image URIs differently, for example. See
+/// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#uris
+///
+typedef bool (*URIEncodeFunction)(const std::string &in_uri,
+                                  const std::string &object_type,
+                                  std::string *out_uri, void *user_data);
+
+///
+/// URIDecodeFunction type. Signature for custom URI decoding of external
+/// resources such as .bin and image files. Used by tinygltf when computing
+/// filenames to write resources.
+///
+typedef bool (*URIDecodeFunction)(const std::string &in_uri,
+                                  std::string *out_uri, void *user_data);
+
+// Declaration of default uri decode function
+bool URIDecode(const std::string &in_uri, std::string *out_uri,
+               void *user_data);
+
+///
+/// A structure containing URI callbacks and a pointer to their user data.
+///
+struct URICallbacks {
+  URIEncodeFunction encode;  // Optional encode method
+  URIDecodeFunction decode;  // Required decode method
+
+  void *user_data;  // An argument that is passed to all uri callbacks
+};
+
 ///
 /// LoadImageDataFunction type. Signature for custom image loading callbacks.
 ///
@@ -1223,7 +1259,9 @@ typedef bool (*LoadImageDataFunction)(Image *, const int, std::string *,
 typedef bool (*WriteImageDataFunction)(const std::string *basepath,
                                        const std::string *filename,
                                        const Image *image, bool embedImages,
-                                       std::string *out_uri, void *user_pointer);
+                                       const URICallbacks *uri_cb,
+                                       std::string *out_uri,
+                                       void *user_pointer);
 
 #ifndef TINYGLTF_NO_STB_IMAGE
 // Declaration of default image loader callback
@@ -1235,8 +1273,8 @@ bool LoadImageData(Image *image, const int image_idx, std::string *err,
 #ifndef TINYGLTF_NO_STB_IMAGE_WRITE
 // Declaration of default image writer callback
 bool WriteImageData(const std::string *basepath, const std::string *filename,
-                    const Image *image, bool embedImages, std::string *out_uri,
-                    void *);
+                    const Image *image, bool embedImages,
+                    const URICallbacks *uri_cb, std::string *out_uri, void *);
 #endif
 
 ///
@@ -1388,6 +1426,11 @@ class TinyGLTF {
   ///
   void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data);
 
+  ///
+  /// Set callbacks to use for URI encoding and decoding and their user data
+  ///
+  void SetURICallbacks(URICallbacks callbacks);
+
   ///
   /// Set callbacks to use for filesystem (fs) access and their user data
   ///
@@ -1470,6 +1513,15 @@ class TinyGLTF {
 #endif
   };
 
+  URICallbacks uri_cb = {
+      // Use paths as-is by default. This will use JSON string escaping.
+      nullptr,
+      // Decode all URIs before using them as paths as the application may have
+      // percent encoded them.
+      &tinygltf::URIDecode,
+      // URI callback user data
+      nullptr};
+
   LoadImageDataFunction LoadImageData =
 #ifndef TINYGLTF_NO_STB_IMAGE
       &tinygltf::LoadImageData;
@@ -2291,6 +2343,13 @@ static const std::string urldecode(const std::string &str) {
 }  // namespace dlib
 // --- dlib end --------------------------------------------------------------
 
+bool URIDecode(const std::string &in_uri, std::string *out_uri,
+               void *user_data) {
+  (void)user_data;
+  *out_uri = dlib::urldecode(in_uri);
+  return true;
+}
+
 static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
                              std::string *warn, const std::string &filename,
                              const std::string &basedir, bool required,
@@ -2501,7 +2560,8 @@ static void WriteToMemory_stbi(void *context, void *data, int size) {
 }
 
 bool WriteImageData(const std::string *basepath, const std::string *filename,
-                    const Image *image, bool embedImages, std::string *out_uri,
+                    const Image *image, bool embedImages,
+                    const URICallbacks *uri_cb, std::string *out_uri,
                     void *fsPtr) {
   const std::string ext = GetFilePathExtension(*filename);
 
@@ -2563,13 +2623,26 @@ bool WriteImageData(const std::string *basepath, const std::string *filename,
     } else {
       // Throw error?
     }
-    *out_uri = *filename;
+    if (uri_cb->encode) {
+      if (!uri_cb->encode(*filename, "image", out_uri, uri_cb->user_data)) {
+        return false;
+      }
+    } else {
+      *out_uri = *filename;
+    }
   }
 
   return true;
 }
 #endif
 
+void TinyGLTF::SetURICallbacks(URICallbacks callbacks) {
+  assert(callbacks.decode);
+  if (callbacks.decode) {
+    uri_cb = callbacks;
+  }
+}
+
 void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; }
 
 #ifdef _WIN32
@@ -2834,15 +2907,21 @@ static std::string MimeToExt(const std::string &mimeType) {
   return "";
 }
 
-static void UpdateImageObject(const Image &image, std::string &baseDir,
+static bool UpdateImageObject(const Image &image, std::string &baseDir,
                               int index, bool embedImages,
+                              const URICallbacks *uri_cb,
                               WriteImageDataFunction *WriteImageData,
-                              std::string *out_uri, void *user_data) {
+                              void *user_data, std::string *out_uri) {
   std::string filename;
   std::string ext;
   // If image has uri, use it as a filename
   if (image.uri.size()) {
-    filename = GetBaseFilename(image.uri);
+    std::string decoded_uri;
+    if (!uri_cb->decode(image.uri, &decoded_uri, uri_cb->user_data)) {
+      // A decode failure results in a failure to write the gltf.
+      return false;
+    }
+    filename = GetBaseFilename(decoded_uri);
     ext = GetFilePathExtension(filename);
   } else if (image.bufferView != -1) {
     // If there's no URI and the data exists in a buffer,
@@ -2857,17 +2936,24 @@ static void UpdateImageObject(const Image &image, std::string &baseDir,
     filename = std::to_string(index) + "." + ext;
   }
 
-  // If callback is set, modify image data object
+  // If callback is set and image data exists, modify image data object. If
+  // image data does not exist, this is not considered a failure and the
+  // original uri should be maintained.
   bool imageWritten = false;
-  if (*WriteImageData != nullptr && !filename.empty()) {
+  if (*WriteImageData != nullptr && !filename.empty() && !image.image.empty()) {
     imageWritten = (*WriteImageData)(&baseDir, &filename, &image, embedImages,
-                                     out_uri, user_data);
+                                     uri_cb, out_uri, user_data);
+    if (!imageWritten) {
+      return false;
+    }
   }
 
   // Use the original uri if the image was not written.
   if (!imageWritten) {
     *out_uri = image.uri;
   }
+
+  return true;
 }
 
 bool IsDataURI(const std::string &in) {
@@ -3786,6 +3872,7 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err,
                        std::string *warn, const json &o,
                        bool store_original_json_for_extras_and_extensions,
                        const std::string &basedir, FsCallbacks *fs,
+                       const URICallbacks *uri_cb,
                        LoadImageDataFunction *LoadImageData = nullptr,
                        void *load_image_user_data = nullptr) {
   // A glTF image must either reference a bufferView or an image uri
@@ -3896,7 +3983,18 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err,
 #ifdef TINYGLTF_NO_EXTERNAL_IMAGE
     return true;
 #else
-    std::string decoded_uri = dlib::urldecode(uri);
+    std::string decoded_uri;
+    if (!uri_cb->decode(uri, &decoded_uri, uri_cb->user_data)) {
+      if (warn) {
+        (*warn) += "Failed to decode 'uri' for image[" +
+                   std::to_string(image_idx) + "] name = [" + image->name +
+                   "]\n";
+      }
+
+      // Image loading failure is not critical to overall gltf loading.
+      return true;
+    }
+
     if (!LoadExternalFile(&img, err, warn, decoded_uri, basedir,
                           /* required */ false, /* required bytes */ 0,
                           /* checksize */ false, fs)) {
@@ -4075,8 +4173,8 @@ static bool ParseOcclusionTextureInfo(
 
 static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
                         bool store_original_json_for_extras_and_extensions,
-                        FsCallbacks *fs, const std::string &basedir,
-                        bool is_binary = false,
+                        FsCallbacks *fs, const URICallbacks *uri_cb,
+                        const std::string &basedir, bool is_binary = false,
                         const unsigned char *bin_data = nullptr,
                         size_t bin_size = 0) {
   size_t byteLength;
@@ -4122,7 +4220,10 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
         }
       } else {
         // External .bin file.
-        std::string decoded_uri = dlib::urldecode(buffer->uri);
+        std::string decoded_uri;
+        if (!uri_cb->decode(buffer->uri, &decoded_uri, uri_cb->user_data)) {
+          return false;
+        }
         if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr,
                               decoded_uri, basedir, /* required */ true,
                               byteLength, /* checkSize */ true, fs)) {
@@ -4167,7 +4268,10 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
       }
     } else {
       // Assume external .bin file.
-      std::string decoded_uri = dlib::urldecode(buffer->uri);
+      std::string decoded_uri;
+      if (!uri_cb->decode(buffer->uri, &decoded_uri, uri_cb->user_data)) {
+        return false;
+      }
       if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri,
                             basedir, /* required */ true, byteLength,
                             /* checkSize */ true, fs)) {
@@ -5000,12 +5104,7 @@ static bool ParseAnimationChannel(
   if (FindMember(o, "target", targetIt) && IsObject(GetValue(targetIt))) {
     const json &target_object = GetValue(targetIt);
 
-    if (!ParseIntegerProperty(&targetIndex, err, target_object, "node", true)) {
-      if (err) {
-        (*err) += "`node` field is missing in animation.channels.target\n";
-      }
-      return false;
-    }
+    ParseIntegerProperty(&targetIndex, err, target_object, "node", false);
 
     if (!ParseStringProperty(&channel->target_path, err, target_object, "path",
                              true)) {
@@ -5710,7 +5809,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
       Buffer buffer;
       if (!ParseBuffer(&buffer, err, o,
                        store_original_json_for_extras_and_extensions_, &fs,
-                       base_dir, is_binary_, bin_data_, bin_size_)) {
+                       &uri_cb, base_dir, is_binary_, bin_data_, bin_size_)) {
         return false;
       }
 
@@ -5981,7 +6080,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
       Image image;
       if (!ParseImage(&image, idx, err, warn, o,
                       store_original_json_for_extras_and_extensions_, base_dir,
-                      &fs, &this->LoadImageData, load_image_user_data)) {
+                      &fs, &uri_cb, &this->LoadImageData,
+                      load_image_user_data)) {
         return false;
       }
 
@@ -6864,7 +6964,11 @@ static void SerializeGltfAnimationChannel(const AnimationChannel &channel,
   SerializeNumberProperty("sampler", channel.sampler, o);
   {
     json target;
-    SerializeNumberProperty("node", channel.target_node, target);
+
+    if (channel.target_node > 0) {
+      SerializeNumberProperty("node", channel.target_node, target);
+    }
+
     SerializeStringProperty("path", channel.target_path, target);
 
     SerializeExtensionMap(channel.target_extensions, target);
@@ -6977,10 +7081,10 @@ static void SerializeGltfBuffer(const Buffer &buffer, json &o) {
 
 static bool SerializeGltfBuffer(const Buffer &buffer, json &o,
                                 const std::string &binFilename,
-                                const std::string &binBaseFilename) {
+                                const std::string &binUri) {
   if (!SerializeGltfBufferData(buffer.data, binFilename)) return false;
   SerializeNumberProperty("byteLength", buffer.data.size(), o);
-  SerializeStringProperty("uri", binBaseFilename, o);
+  SerializeStringProperty("uri", binUri, o);
 
   if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o);
 
@@ -7016,7 +7120,7 @@ static void SerializeGltfBufferView(const BufferView &bufferView, json &o) {
   }
 }
 
-static void SerializeGltfImage(const Image &image, const std::string uri,
+static void SerializeGltfImage(const Image &image, const std::string &uri,
                                json &o) {
   // From 2.7.0, we look for `uri` parameter, not `Image.uri`
   // if uri is empty, the mimeType and bufferview should be set
@@ -7024,7 +7128,6 @@ static void SerializeGltfImage(const Image &image, const std::string uri,
     SerializeStringProperty("mimeType", image.mimeType, o);
     SerializeNumberProperty<int>("bufferView", image.bufferView, o);
   } else {
-    // TODO(syoyo): dlib::urilencode?
     SerializeStringProperty("uri", uri, o);
   }
 
@@ -7338,6 +7441,9 @@ static void SerializeGltfNode(const Node &node, json &o) {
 }
 
 static void SerializeGltfSampler(const Sampler &sampler, json &o) {
+  if (!sampler.name.empty()) {
+    SerializeStringProperty("name", sampler.name, o);
+  }
   if (sampler.magFilter != -1) {
     SerializeNumberProperty("magFilter", sampler.magFilter, o);
   }
@@ -7817,9 +7923,11 @@ bool TinyGLTF::WriteGltfSceneToStream(const Model *model, std::ostream &stream,
       // enabled, since we won't write separate images when writing to a stream
       // we
       std::string uri;
-      UpdateImageObject(model->images[i], dummystring, int(i), true,
-                        &this->WriteImageData, &uri,
-                        this->write_image_user_data_);
+      if (!UpdateImageObject(model->images[i], dummystring, int(i), true,
+                             &uri_cb, &this->WriteImageData,
+                             this->write_image_user_data_, &uri)) {
+        return false;
+      }
       SerializeGltfImage(model->images[i], uri, image);
       JsonPushBack(images, std::move(image));
     }
@@ -7856,7 +7964,7 @@ bool TinyGLTF::WriteGltfSceneToFile(const Model *model,
   SerializeGltfModel(model, output);
 
   // BUFFERS
-  std::vector<std::string> usedUris;
+  std::vector<std::string> usedFilenames;
   std::vector<unsigned char> binBuffer;
   if (model->buffers.size()) {
     json buffers;
@@ -7869,27 +7977,40 @@ bool TinyGLTF::WriteGltfSceneToFile(const Model *model,
         SerializeGltfBuffer(model->buffers[i], buffer);
       } else {
         std::string binSavePath;
+        std::string binFilename;
         std::string binUri;
         if (!model->buffers[i].uri.empty() &&
             !IsDataURI(model->buffers[i].uri)) {
           binUri = model->buffers[i].uri;
+          if (!uri_cb.decode(binUri, &binFilename, uri_cb.user_data)) {
+            return false;
+          }
         } else {
-          binUri = defaultBinFilename + defaultBinFileExt;
+          binFilename = defaultBinFilename + defaultBinFileExt;
           bool inUse = true;
           int numUsed = 0;
           while (inUse) {
             inUse = false;
-            for (const std::string &usedName : usedUris) {
-              if (binUri.compare(usedName) != 0) continue;
+            for (const std::string &usedName : usedFilenames) {
+              if (binFilename.compare(usedName) != 0) continue;
               inUse = true;
-              binUri = defaultBinFilename + std::to_string(numUsed++) +
-                       defaultBinFileExt;
+              binFilename = defaultBinFilename + std::to_string(numUsed++) +
+                            defaultBinFileExt;
               break;
             }
           }
+
+          if (uri_cb.encode) {
+            if (!uri_cb.encode(binFilename, "buffer", &binUri,
+                               uri_cb.user_data)) {
+              return false;
+            }
+          } else {
+            binUri = binFilename;
+          }
         }
-        usedUris.push_back(binUri);
-        binSavePath = JoinPath(baseDir, binUri);
+        usedFilenames.push_back(binFilename);
+        binSavePath = JoinPath(baseDir, binFilename);
         if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath,
                                  binUri)) {
           return false;
@@ -7908,9 +8029,11 @@ bool TinyGLTF::WriteGltfSceneToFile(const Model *model,
       json image;
 
       std::string uri;
-      UpdateImageObject(model->images[i], baseDir, int(i), embedImages,
-                        &this->WriteImageData, &uri,
-                        this->write_image_user_data_);
+      if (!UpdateImageObject(model->images[i], baseDir, int(i), embedImages,
+                             &uri_cb, &this->WriteImageData,
+                             this->write_image_user_data_, &uri)) {
+        return false;
+      }
       SerializeGltfImage(model->images[i], uri, image);
       JsonPushBack(images, std::move(image));
     }

More details

Full run details

Historical runs