diff --git a/tests/tester.cc b/tests/tester.cc index 6b10979..b06c30d 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -1199,4 +1199,51 @@ TEST_CASE("inverse-bind-matrices-optional", "[issue-492]") { REQUIRE(true == ret); REQUIRE(err.empty()); -} \ No newline at end of file +} + +bool LoadImageData(tinygltf::Image * /* image */, const int /* image_idx */, std::string * /* err */, + std::string * /* warn */, int /* req_width */, int /* req_height */, + const unsigned char * /* bytes */, int /* size */, void * /*user_data */) { + return true; +} + +bool WriteImageData(const std::string * /* basepath */, const std::string * /* filename */, + const tinygltf::Image *image, bool /* embedImages */, + const tinygltf::FsCallbacks * /* fs_cb */, const tinygltf::URICallbacks * /* uri_cb */, + std::string * /* out_uri */, void * user_pointer) { + REQUIRE(user_pointer != nullptr); + auto counter = static_cast(user_pointer); + *counter = *counter + 1; + return true; +} + +TEST_CASE("empty-images-not-written", "[issue-495]") { + std::string err; + std::string warn; + tinygltf::Model model; + tinygltf::TinyGLTF ctx; + + ctx.SetImageLoader(LoadImageData, nullptr); + bool ok = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/Cube/Cube.gltf"); + REQUIRE(ok); + REQUIRE(err.empty()); + REQUIRE(warn.empty()); + + CHECK(model.images.size() == 2); + for (const auto& image : model.images) { + // No data loaded or decoded + CHECK(image.image.empty()); + // The URI is kept + CHECK_FALSE(image.uri.empty()); + // The URI should not be a data URI + CHECK(image.uri.find("data:") != 0); + } + + // Now write the loaded model + int counter = 0; + ctx.SetImageWriter(WriteImageData, &counter); + ok = ctx.WriteGltfSceneToFile(&model, "issue-495-external.gltf"); + CHECK(ok); + // WriteImageData should be invoked for both images + CHECK(counter == 2); +} diff --git a/tiny_gltf.h b/tiny_gltf.h index 2fa0c21..669cb65 100644 --- a/tiny_gltf.h +++ b/tiny_gltf.h @@ -2767,6 +2767,12 @@ bool WriteImageData(const std::string *basepath, const std::string *filename, const Image *image, bool embedImages, const FsCallbacks* fs_cb, const URICallbacks *uri_cb, std::string *out_uri, void *) { + // Early out on empty images, report the original uri if the image was not written. + if (image->image.empty()) { + *out_uri = *filename; + return true; + } + const std::string ext = GetFilePathExtension(*filename); // Write image to temporary buffer @@ -3309,11 +3315,12 @@ static bool UpdateImageObject(const Image &image, std::string &baseDir, filename = std::to_string(index) + "." + ext; } - // 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. + // If callback is set, modify image data object. + // Note that the callback is also invoked for images without data. + // The default callback implementation simply returns true for + // empty images and sets the out URI to filename. bool imageWritten = false; - if (WriteImageData != nullptr && !filename.empty() && !image.image.empty()) { + if (WriteImageData != nullptr && !filename.empty()) { imageWritten = WriteImageData(&baseDir, &filename, &image, embedImages, fs_cb, uri_cb, out_uri, user_data); if (!imageWritten) { @@ -4387,7 +4394,7 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, } } else { // Assume external file - // Keep texture path (for textures that cannot be decoded) + // Unconditionally keep the external URI of the image image->uri = uri; #ifdef TINYGLTF_NO_EXTERNAL_IMAGE return true; @@ -4434,6 +4441,7 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, } return false; } + return LoadImageData(image, image_idx, err, warn, 0, 0, &img.at(0), static_cast(img.size()), load_image_user_data); }