mirror of
https://git.mirrors.martin98.com/https://github.com/syoyo/tinygltf.git
synced 2025-07-08 00:51:46 +08:00
Merge remote-tracking branch 'origin/devel' into generic_extension_support
This commit is contained in:
commit
5210f1539e
@ -47,13 +47,20 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch.
|
|||||||
* [glview](examples/glview) : Simple glTF geometry viewer.
|
* [glview](examples/glview) : Simple glTF geometry viewer.
|
||||||
* [validator](examples/validator) : Simple glTF validator with JSON schema.
|
* [validator](examples/validator) : Simple glTF validator with JSON schema.
|
||||||
|
|
||||||
|
## Projects using TinyGLTF
|
||||||
|
|
||||||
|
* Physical based rendering with Vulkan using glTF 2.0 models https://github.com/SaschaWillems/Vulkan-glTF-PBR
|
||||||
|
* GLTF loader plugin for OGRE 2.1. Support for PBR materials via HLMS/PBS https://github.com/Ybalrid/Ogre_glTF
|
||||||
|
* Your projects here!(Plese send PR)
|
||||||
|
|
||||||
## TODOs
|
## TODOs
|
||||||
|
|
||||||
* [ ] Write C++ code generator from json schema for robust parsing.
|
* [ ] Write C++ code generator from jSON schema for robust parsing.
|
||||||
* [x] Serialization
|
* [x] Serialization
|
||||||
* [ ] Compression/decompression(Open3DGC, etc)
|
* [ ] Compression/decompression(Open3DGC, etc)
|
||||||
* [ ] Support `extensions` and `extras` property
|
* [ ] Support `extensions` and `extras` property
|
||||||
* [ ] HDR image?
|
* [ ] HDR image?
|
||||||
|
* [ ] OpenEXR extension through TinyEXR.
|
||||||
* [ ] Write tests for `animation` and `skin`
|
* [ ] Write tests for `animation` and `skin`
|
||||||
|
|
||||||
## Licenses
|
## Licenses
|
||||||
|
9
examples/gltfutil/CMakeLists.txt
Normal file
9
examples/gltfutil/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.6)
|
||||||
|
project(gltfutil)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
|
||||||
|
include_directories(../../)
|
||||||
|
|
||||||
|
file(GLOB gltfutil_sources *.cc *.h)
|
||||||
|
add_executable(gltfutil ${gltfutil_sources})
|
60
examples/gltfutil/gltfuilconfig.h
Normal file
60
examples/gltfutil/gltfuilconfig.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "texture_dumper.h"
|
||||||
|
|
||||||
|
namespace gltfutil {
|
||||||
|
|
||||||
|
enum class ui_mode { cli, interactive };
|
||||||
|
enum class cli_action { not_set, help, dump };
|
||||||
|
enum class FileType { Ascii, Binary, Unknown };
|
||||||
|
|
||||||
|
/// Probe inside the file, or check the extension to determine if we have to
|
||||||
|
/// load a text file, or a binary file
|
||||||
|
FileType detectType(const std::string& path) {
|
||||||
|
// Quickly open the file as binary and chekc if there's the gltf binary magic
|
||||||
|
// number
|
||||||
|
{
|
||||||
|
auto probe = std::ifstream(path, std::ios_base::binary);
|
||||||
|
if (!probe) throw std::runtime_error("Could not open " + path);
|
||||||
|
|
||||||
|
std::array<char, 5> buffer;
|
||||||
|
for (size_t i{0}; i < 4; ++i) probe >> buffer[i];
|
||||||
|
buffer[4] = 0;
|
||||||
|
|
||||||
|
if (std::string("glTF") == std::string(buffer.data())) {
|
||||||
|
std::cout
|
||||||
|
<< "Detected binary file thanks to the magic number at the start!\n";
|
||||||
|
return FileType::Binary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have any better, check the file extension.
|
||||||
|
auto extension = path.substr(path.find_last_of('.') + 1);
|
||||||
|
std::transform(std::begin(extension), std::end(extension),
|
||||||
|
std::begin(extension),
|
||||||
|
[](char c) { return char(::tolower(int(c))); });
|
||||||
|
if (extension == "gltf") return FileType::Ascii;
|
||||||
|
if (extension == "glb") return FileType::Binary;
|
||||||
|
|
||||||
|
return FileType::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct configuration {
|
||||||
|
std::string input_path, output_dir;
|
||||||
|
ui_mode mode;
|
||||||
|
cli_action action = cli_action::not_set;
|
||||||
|
texture_dumper::texture_output_format requested_format =
|
||||||
|
texture_dumper::texture_output_format::not_specified;
|
||||||
|
|
||||||
|
bool has_output_dir;
|
||||||
|
bool is_valid() {
|
||||||
|
// TODO impl check
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gltfutil
|
123
examples/gltfutil/main.cc
Normal file
123
examples/gltfutil/main.cc
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "gltfuilconfig.h"
|
||||||
|
#include "texture_dumper.h"
|
||||||
|
|
||||||
|
#define TINYGLTF_IMPLEMENTATION
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include <tiny_gltf.h>
|
||||||
|
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include "stb_image_write.h"
|
||||||
|
|
||||||
|
namespace gltfutil {
|
||||||
|
int usage(int ret = 0) {
|
||||||
|
using std::cout;
|
||||||
|
cout << "gltfutil: tool for manipulating gltf files\n"
|
||||||
|
<< " usage information:\n\n"
|
||||||
|
<< "\t gltfutil (-d|-h|) (-f [png|bmp|tga]) [path to .gltf/glb] (-o "
|
||||||
|
"[path to output directory])\n\n"
|
||||||
|
//<< "\t\t -i: start in interactive mode\n"
|
||||||
|
<< "\t\t -d: dump enclosed content (image assets)\n"
|
||||||
|
<< "\t\t -f: file format for image output"
|
||||||
|
<< "\t\t -o: ouptput directory path"
|
||||||
|
<< "\t\t -h: print this help\n";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arg_error() {
|
||||||
|
(void)usage();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_args(int argc, char** argv) {
|
||||||
|
gltfutil::configuration config;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < size_t(argc); ++i) {
|
||||||
|
char* arg = argv[i];
|
||||||
|
if (arg[0] == '-') switch (arg[1]) {
|
||||||
|
case 'h':
|
||||||
|
return usage(0);
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
config.mode = ui_mode::cli;
|
||||||
|
config.action = cli_action::dump;
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
config.mode = ui_mode::interactive;
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
i++;
|
||||||
|
config.output_dir = argv[i];
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
i++;
|
||||||
|
config.requested_format =
|
||||||
|
texture_dumper::get_fromat_from_string(argv[i]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return arg_error();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// set the input path
|
||||||
|
config.input_path = argv[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.is_valid()) {
|
||||||
|
tinygltf::TinyGLTF loader;
|
||||||
|
tinygltf::Model model;
|
||||||
|
std::string error;
|
||||||
|
bool state;
|
||||||
|
switch (detectType(config.input_path)) {
|
||||||
|
case FileType::Ascii:
|
||||||
|
state = loader.LoadASCIIFromFile(&model, &error, config.input_path);
|
||||||
|
break;
|
||||||
|
case FileType::Binary:
|
||||||
|
state = loader.LoadBinaryFromFile(&model, &error, config.input_path);
|
||||||
|
break;
|
||||||
|
case FileType::Unknown:
|
||||||
|
default:
|
||||||
|
return arg_error();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "state is " << state << '\n';
|
||||||
|
|
||||||
|
if (config.mode == ui_mode::interactive) {
|
||||||
|
// interactive usage now;
|
||||||
|
// TODO impl
|
||||||
|
} else {
|
||||||
|
switch (config.action) {
|
||||||
|
case cli_action::help:
|
||||||
|
return usage();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case cli_action::dump: {
|
||||||
|
texture_dumper dumper(model);
|
||||||
|
if (config.requested_format !=
|
||||||
|
texture_dumper::texture_output_format::not_specified)
|
||||||
|
dumper.set_output_format(config.requested_format);
|
||||||
|
|
||||||
|
if (config.output_dir.empty())
|
||||||
|
dumper.dump_to_folder();
|
||||||
|
else
|
||||||
|
dumper.dump_to_folder(config.output_dir);
|
||||||
|
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
return arg_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return arg_error();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace gltfutil
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (argc == 1) return gltfutil::usage();
|
||||||
|
if (argc > 1) return gltfutil::parse_args(argc, argv);
|
||||||
|
}
|
1830
examples/gltfutil/stb_image_write.h
Normal file
1830
examples/gltfutil/stb_image_write.h
Normal file
File diff suppressed because it is too large
Load Diff
67
examples/gltfutil/texture_dumper.cc
Normal file
67
examples/gltfutil/texture_dumper.cc
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "stb_image_write.h"
|
||||||
|
#include "texture_dumper.h"
|
||||||
|
|
||||||
|
#include <tiny_gltf.h>
|
||||||
|
|
||||||
|
using namespace gltfutil;
|
||||||
|
using namespace tinygltf;
|
||||||
|
using std::cout;
|
||||||
|
|
||||||
|
texture_dumper::texture_dumper(const Model& input)
|
||||||
|
: model(input), configured_format(texture_output_format::png) {
|
||||||
|
cout << "Texture dumper\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void texture_dumper::dump_to_folder(const std::string& path) {
|
||||||
|
cout << "dumping to folder " << path << '\n';
|
||||||
|
cout << "model file has " << model.textures.size() << " textures.\n";
|
||||||
|
size_t index = 0;
|
||||||
|
|
||||||
|
for (const auto& texture : model.textures) {
|
||||||
|
index++;
|
||||||
|
const auto& image = model.images[texture.source];
|
||||||
|
cout << "image name is: \"" << image.name << "\"\n";
|
||||||
|
cout << "image size is: " << image.width << 'x' << image.height << '\n';
|
||||||
|
cout << "pixel channel count :" << image.component << '\n';
|
||||||
|
std::string name = image.name.empty() ? std::to_string(index) : image.name;
|
||||||
|
|
||||||
|
switch (configured_format) {
|
||||||
|
case texture_output_format::png:
|
||||||
|
name = path + "/" + name + ".png";
|
||||||
|
std::cout << "Image will be written to " << name << '\n';
|
||||||
|
stbi_write_png(name.c_str(), image.width, image.height, image.component,
|
||||||
|
image.image.data(), 0);
|
||||||
|
break;
|
||||||
|
case texture_output_format::bmp:
|
||||||
|
std::cout << "Image will be written to " << name << '\n';
|
||||||
|
name = path + "/" + name + ".bmp";
|
||||||
|
stbi_write_bmp(name.c_str(), image.width, image.height, image.component,
|
||||||
|
image.image.data());
|
||||||
|
break;
|
||||||
|
case texture_output_format::tga:
|
||||||
|
std::cout << "Image will be written to " << name << '\n';
|
||||||
|
name = path + "/" + name + ".tga";
|
||||||
|
stbi_write_tga(name.c_str(), image.width, image.height, image.component,
|
||||||
|
image.image.data());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void texture_dumper::set_output_format(texture_output_format format) {
|
||||||
|
configured_format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
texture_dumper::texture_output_format texture_dumper::get_fromat_from_string(
|
||||||
|
const std::string& str) {
|
||||||
|
std::string type = str;
|
||||||
|
std::transform(str.begin(), str.end(), type.begin(), ::tolower);
|
||||||
|
|
||||||
|
if (type == "png") return texture_output_format::png;
|
||||||
|
if (type == "bmp") return texture_output_format::bmp;
|
||||||
|
if (type == "tga") return texture_output_format::tga;
|
||||||
|
|
||||||
|
return texture_output_format::not_specified;
|
||||||
|
}
|
23
examples/gltfutil/texture_dumper.h
Normal file
23
examples/gltfutil/texture_dumper.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <tiny_gltf.h>
|
||||||
|
|
||||||
|
namespace gltfutil {
|
||||||
|
class texture_dumper {
|
||||||
|
public:
|
||||||
|
enum class texture_output_format { png, bmp, tga, not_specified };
|
||||||
|
|
||||||
|
private:
|
||||||
|
const tinygltf::Model& model;
|
||||||
|
texture_output_format configured_format;
|
||||||
|
|
||||||
|
public:
|
||||||
|
texture_dumper(const tinygltf::Model& inputModel);
|
||||||
|
void dump_to_folder(const std::string& path = "./");
|
||||||
|
void set_output_format(texture_output_format format);
|
||||||
|
|
||||||
|
static texture_output_format get_fromat_from_string(const std::string& str);
|
||||||
|
};
|
||||||
|
} // namespace gltfutil
|
163
tiny_gltf.h
163
tiny_gltf.h
@ -734,9 +734,9 @@ typedef bool (*LoadImageDataFunction)(Image *, std::string *, int, int,
|
|||||||
|
|
||||||
#ifndef TINYGLTF_NO_STB_IMAGE
|
#ifndef TINYGLTF_NO_STB_IMAGE
|
||||||
// Declaration of default image loader callback
|
// Declaration of default image loader callback
|
||||||
static bool LoadImageData(Image *image, std::string *err, int req_width,
|
bool LoadImageData(Image *image, std::string *err, int req_width,
|
||||||
int req_height, const unsigned char *bytes, int size,
|
int req_height, const unsigned char *bytes, int size,
|
||||||
void *);
|
void *);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class TinyGLTF {
|
class TinyGLTF {
|
||||||
@ -1199,9 +1199,9 @@ void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef TINYGLTF_NO_STB_IMAGE
|
#ifndef TINYGLTF_NO_STB_IMAGE
|
||||||
static bool LoadImageData(Image *image, std::string *err, int req_width,
|
bool LoadImageData(Image *image, std::string *err, int req_width,
|
||||||
int req_height, const unsigned char *bytes, int size,
|
int req_height, const unsigned char *bytes, int size,
|
||||||
void *) {
|
void *) {
|
||||||
int w, h, comp;
|
int w, h, comp;
|
||||||
// if image cannot be decoded, ignore parsing and keep it by its path
|
// if image cannot be decoded, ignore parsing and keep it by its path
|
||||||
// don't break in this case
|
// don't break in this case
|
||||||
@ -1736,100 +1736,97 @@ static bool ParseImage(Image *image, std::string *err, const json &o,
|
|||||||
LoadImageDataFunction *LoadImageData = nullptr,
|
LoadImageDataFunction *LoadImageData = nullptr,
|
||||||
void *user_data = nullptr) {
|
void *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
|
||||||
double bufferView = -1;
|
|
||||||
bool isEmbedded =
|
|
||||||
ParseNumberProperty(&bufferView, err, o, "bufferView", false);
|
|
||||||
|
|
||||||
std::string uri;
|
// schema says oneOf [`bufferView`, `uri`]
|
||||||
std::string tmp_err;
|
// TODO(syoyo): Check the type of each parameters.
|
||||||
if (!ParseStringProperty(&uri, &tmp_err, o, "uri", false) && !isEmbedded) {
|
bool hasBufferView = (o.find("bufferView") != o.end());
|
||||||
|
bool hasURI = (o.find("uri") != o.end());
|
||||||
|
|
||||||
|
if (hasBufferView && hasURI) {
|
||||||
|
// Should not both defined.
|
||||||
if (err) {
|
if (err) {
|
||||||
(*err) += "`bufferView` or `uri` required for Image.\n";
|
(*err) += "Only one of `bufferView` or `uri` should be defined, but both are defined for Image.\n";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasBufferView && !hasURI) {
|
||||||
|
if (err) {
|
||||||
|
(*err) += "Neither required `bufferView` nor `uri` defined for Image.\n";
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseStringProperty(&image->name, err, o, "name", false);
|
ParseStringProperty(&image->name, err, o, "name", false);
|
||||||
|
|
||||||
|
if (hasBufferView) {
|
||||||
|
double bufferView = -1;
|
||||||
|
if (!ParseNumberProperty(&bufferView, err, o, "bufferView", true)) {
|
||||||
|
if (err) {
|
||||||
|
(*err) += "Failed to parse `bufferView` for Image.\n";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mime_type;
|
||||||
|
ParseStringProperty(&mime_type, err, o, "mimeType", false);
|
||||||
|
|
||||||
|
double width = 0.0;
|
||||||
|
ParseNumberProperty(&width, err, o, "width", false);
|
||||||
|
|
||||||
|
double height = 0.0;
|
||||||
|
ParseNumberProperty(&height, err, o, "height", false);
|
||||||
|
|
||||||
|
// Just only save some information here. Loading actual image data from
|
||||||
|
// bufferView is done after this `ParseImage` function.
|
||||||
|
image->bufferView = static_cast<int>(bufferView);
|
||||||
|
image->mimeType = mime_type;
|
||||||
|
image->width = static_cast<int>(width);
|
||||||
|
image->height = static_cast<int>(height);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse URI & Load image data.
|
||||||
|
|
||||||
|
std::string uri;
|
||||||
|
std::string tmp_err;
|
||||||
|
if (!ParseStringProperty(&uri, &tmp_err, o, "uri", true)) {
|
||||||
|
if (err) {
|
||||||
|
(*err) += "Failed to parse `uri` for Image.\n";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> img;
|
std::vector<unsigned char> img;
|
||||||
|
|
||||||
if (is_binary) {
|
if (IsDataURI(uri)) {
|
||||||
// Still binary glTF accepts external dataURI. First try external resources.
|
if (!DecodeDataURI(&img, uri, 0, false)) {
|
||||||
bool loaded = false;
|
if (err) {
|
||||||
if (IsDataURI(uri)) {
|
(*err) += "Failed to decode 'uri' for image parameter.\n";
|
||||||
loaded = DecodeDataURI(&img, uri, 0, false);
|
|
||||||
} else {
|
|
||||||
// Assume external file
|
|
||||||
// Keep texture path (for textures that cannot be decoded)
|
|
||||||
image->uri = uri;
|
|
||||||
#ifdef TINYGLTF_NO_EXTERNAL_IMAGE
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
loaded = LoadExternalFile(&img, err, uri, basedir, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!loaded) {
|
|
||||||
// load data from (embedded) binary data
|
|
||||||
|
|
||||||
if ((bin_size == 0) || (bin_data == nullptr)) {
|
|
||||||
if (err) {
|
|
||||||
(*err) += "Invalid binary data.\n";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
double buffer_view = -1.0;
|
|
||||||
if (!ParseNumberProperty(&buffer_view, err, o, "bufferView", true,
|
|
||||||
"Image")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string mime_type;
|
|
||||||
ParseStringProperty(&mime_type, err, o, "mimeType", false);
|
|
||||||
|
|
||||||
double width = 0.0;
|
|
||||||
ParseNumberProperty(&width, err, o, "width", false);
|
|
||||||
|
|
||||||
double height = 0.0;
|
|
||||||
ParseNumberProperty(&height, err, o, "height", false);
|
|
||||||
|
|
||||||
// Just only save some information here. Loading actual image data from
|
|
||||||
// bufferView is done in other place.
|
|
||||||
image->bufferView = static_cast<int>(buffer_view);
|
|
||||||
image->mimeType = mime_type;
|
|
||||||
image->width = static_cast<int>(width);
|
|
||||||
image->height = static_cast<int>(height);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (IsDataURI(uri)) {
|
// Assume external file
|
||||||
if (!DecodeDataURI(&img, uri, 0, false)) {
|
// Keep texture path (for textures that cannot be decoded)
|
||||||
if (err) {
|
image->uri = uri;
|
||||||
(*err) += "Failed to decode 'uri' for image parameter.\n";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Assume external file
|
|
||||||
// Keep texture path (for textures that cannot be decoded)
|
|
||||||
image->uri = uri;
|
|
||||||
#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)) {
|
||||||
if (err) {
|
if (err) {
|
||||||
(*err) += "Failed to load external 'uri' for image parameter\n";
|
(*err) += "Failed to load external 'uri' for image parameter\n";
|
||||||
}
|
|
||||||
// If the image cannot be loaded, keep uri as image->uri.
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (img.empty()) {
|
// If the image cannot be loaded, keep uri as image->uri.
|
||||||
if (err) {
|
return true;
|
||||||
(*err) += "Image is empty.\n";
|
}
|
||||||
}
|
|
||||||
return false;
|
if (img.empty()) {
|
||||||
|
if (err) {
|
||||||
|
(*err) += "Image is empty.\n";
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user