Merge pull request #42504 from akien-mga/glTF-fix-image-loading

glTF: Fix parsing image data with `mimeType` undefined
This commit is contained in:
Rémi Verschelde 2020-10-05 15:02:18 +02:00 committed by GitHub
commit 7d312448ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 86 additions and 53 deletions

View file

@ -381,19 +381,15 @@ Error EditorSceneImporterGLTF::_parse_buffers(GLTFState &state, const String &p_
if (uri.begins_with("data:")) { // Embedded data using base64. if (uri.begins_with("data:")) { // Embedded data using base64.
// Validate data MIME types and throw an error if it's one we don't know/support. // Validate data MIME types and throw an error if it's one we don't know/support.
// Could be an importer bug on our side or a broken glTF file.
// Ref: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#file-extensions-and-mime-types
if (!uri.begins_with("data:application/octet-stream;base64") && if (!uri.begins_with("data:application/octet-stream;base64") &&
!uri.begins_with("data:application/gltf-buffer;base64") && !uri.begins_with("data:application/gltf-buffer;base64")) {
!uri.begins_with("data:image/jpeg;base64") && ERR_PRINT("glTF: Got buffer with an unknown URI data type: " + uri);
!uri.begins_with("data:image/png;base64")) {
ERR_PRINT("glTF file contains buffer with an unknown URI data type: " + uri);
} }
buffer_data = _parse_base64_uri(uri); buffer_data = _parse_base64_uri(uri);
} else { // Should be a relative file path. } else { // Relative path to an external image file.
uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows.
buffer_data = FileAccess::get_file_as_array(uri); buffer_data = FileAccess::get_file_as_array(uri);
ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "Couldn't load binary file as an array: " + uri); ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri);
} }
ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR); ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR);
@ -1269,12 +1265,28 @@ Error EditorSceneImporterGLTF::_parse_images(GLTFState &state, const String &p_b
return OK; return OK;
} }
// Ref: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#images
const Array &images = state.json["images"]; const Array &images = state.json["images"];
for (int i = 0; i < images.size(); i++) { for (int i = 0; i < images.size(); i++) {
const Dictionary &d = images[i]; const Dictionary &d = images[i];
// glTF 2.0 supports PNG and JPEG types, which can be specified as (from spec):
// "- a URI to an external file in one of the supported images formats, or
// - a URI with embedded base64-encoded data, or
// - a reference to a bufferView; in that case mimeType must be defined."
// Since mimeType is optional for external files and base64 data, we'll have to
// fall back on letting Godot parse the data to figure out if it's PNG or JPEG.
// We'll assume that we use either URI or bufferView, so let's warn the user
// if their image somehow uses both. And fail if it has neither.
ERR_CONTINUE_MSG(!d.has("uri") && !d.has("bufferView"), "Invalid image definition in glTF file, it should specific an 'uri' or 'bufferView'.");
if (d.has("uri") && d.has("bufferView")) {
WARN_PRINT("Invalid image definition in glTF file using both 'uri' and 'bufferView'. 'bufferView' will take precedence.");
}
String mimetype; String mimetype;
if (d.has("mimeType")) { if (d.has("mimeType")) { // Should be "image/png" or "image/jpeg".
mimetype = d["mimeType"]; mimetype = d["mimeType"];
} }
@ -1283,23 +1295,52 @@ Error EditorSceneImporterGLTF::_parse_images(GLTFState &state, const String &p_b
int data_size = 0; int data_size = 0;
if (d.has("uri")) { if (d.has("uri")) {
// Handles the first two bullet points from the spec (embedded data, or external file).
String uri = d["uri"]; String uri = d["uri"];
if (uri.findn("data:application/octet-stream;base64") == 0 || if (uri.begins_with("data:")) { // Embedded data using base64.
uri.findn("data:" + mimetype + ";base64") == 0) { // Validate data MIME types and throw an error if it's one we don't know/support.
//embedded data if (!uri.begins_with("data:application/octet-stream;base64") &&
!uri.begins_with("data:application/gltf-buffer;base64") &&
!uri.begins_with("data:image/png;base64") &&
!uri.begins_with("data:image/jpeg;base64")) {
ERR_PRINT("glTF: Got image data with an unknown URI data type: " + uri);
}
data = _parse_base64_uri(uri); data = _parse_base64_uri(uri);
data_ptr = data.ptr(); data_ptr = data.ptr();
data_size = data.size(); data_size = data.size();
} else { // mimeType is optional, but if we have it defined in the URI, let's use it.
uri = p_base_path.plus_file(uri).replace("\\", "/"); //fix for windows if (mimetype.empty()) {
Ref<Texture2D> texture = ResourceLoader::load(uri); if (uri.begins_with("data:image/png;base64")) {
state.images.push_back(texture); mimetype = "image/png";
continue; } else if (uri.begins_with("data:image/jpeg;base64")) {
mimetype = "image/jpeg";
}
}
} else { // Relative path to an external image file.
uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows.
// The spec says that if mimeType is defined, we should enforce it.
// So we should only rely on ResourceLoader::load if mimeType is not defined,
// otherwise we should use the same logic as for buffers.
if (mimetype == "image/png" || mimetype == "image/jpeg") {
// Load data buffer and rely on PNG and JPEG-specific logic below to load the image.
// This makes it possible to load a file with a wrong extension but correct MIME type,
// e.g. "foo.jpg" containing PNG data and with MIME type "image/png". ResourceLoader would fail.
data = FileAccess::get_file_as_array(uri);
ERR_FAIL_COND_V_MSG(data.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load image file as an array: " + uri);
data_ptr = data.ptr();
data_size = data.size();
} else {
// Good old ResourceLoader will rely on file extension.
Ref<Texture2D> texture = ResourceLoader::load(uri);
state.images.push_back(texture);
continue;
}
} }
} } else if (d.has("bufferView")) {
// Handles the third bullet point from the spec (bufferView).
ERR_FAIL_COND_V_MSG(mimetype.empty(), ERR_FILE_CORRUPT, "glTF: Image specifies 'bufferView' but no 'mimeType', which is invalid.");
if (d.has("bufferView")) {
const GLTFBufferViewIndex bvi = d["bufferView"]; const GLTFBufferViewIndex bvi = d["bufferView"];
ERR_FAIL_INDEX_V(bvi, state.buffer_views.size(), ERR_PARAMETER_RANGE_ERROR); ERR_FAIL_INDEX_V(bvi, state.buffer_views.size(), ERR_PARAMETER_RANGE_ERROR);
@ -1315,45 +1356,36 @@ Error EditorSceneImporterGLTF::_parse_images(GLTFState &state, const String &p_b
data_size = bv.byte_length; data_size = bv.byte_length;
} }
ERR_FAIL_COND_V(mimetype == "", ERR_FILE_CORRUPT); Ref<Image> img;
if (mimetype.findn("png") != -1) { if (mimetype == "image/png") { // Load buffer as PNG.
//is a png
ERR_FAIL_COND_V(Image::_png_mem_loader_func == nullptr, ERR_UNAVAILABLE); ERR_FAIL_COND_V(Image::_png_mem_loader_func == nullptr, ERR_UNAVAILABLE);
img = Image::_png_mem_loader_func(data_ptr, data_size);
const Ref<Image> img = Image::_png_mem_loader_func(data_ptr, data_size); } else if (mimetype == "image/jpeg") { // Loader buffer as JPEG.
ERR_FAIL_COND_V(img.is_null(), ERR_FILE_CORRUPT);
Ref<ImageTexture> t;
t.instance();
t->create_from_image(img);
state.images.push_back(t);
continue;
}
if (mimetype.findn("jpeg") != -1) {
//is a jpg
ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == nullptr, ERR_UNAVAILABLE); ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == nullptr, ERR_UNAVAILABLE);
img = Image::_jpg_mem_loader_func(data_ptr, data_size);
const Ref<Image> img = Image::_jpg_mem_loader_func(data_ptr, data_size); } else {
// We can land here if we got an URI with base64-encoded data with application/* MIME type,
ERR_FAIL_COND_V(img.is_null(), ERR_FILE_CORRUPT); // and the optional mimeType property was not defined to tell us how to handle this data (or was invalid).
// So let's try PNG first, then JPEG.
Ref<ImageTexture> t; ERR_FAIL_COND_V(Image::_png_mem_loader_func == nullptr, ERR_UNAVAILABLE);
t.instance(); img = Image::_png_mem_loader_func(data_ptr, data_size);
t->create_from_image(img); if (img.is_null()) {
ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == nullptr, ERR_UNAVAILABLE);
state.images.push_back(t); img = Image::_jpg_mem_loader_func(data_ptr, data_size);
}
continue;
} }
ERR_FAIL_V(ERR_FILE_CORRUPT); ERR_FAIL_COND_V_MSG(img.is_null(), ERR_FILE_CORRUPT, "glTF: Couldn't load image with its given mimetype: " + mimetype);
Ref<ImageTexture> t;
t.instance();
t->create_from_image(img);
state.images.push_back(t);
} }
print_verbose("Total images: " + itos(state.images.size())); print_verbose("glTF: Total images: " + itos(state.images.size()));
return OK; return OK;
} }
@ -1513,7 +1545,7 @@ Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) {
state.materials.push_back(material); state.materials.push_back(material);
} }
print_verbose("Total materials: " + itos(state.materials.size())); print_verbose("glTF: Total materials: " + itos(state.materials.size()));
return OK; return OK;
} }
@ -3064,6 +3096,8 @@ Node3D *EditorSceneImporterGLTF::_generate_scene(GLTFState &state, const int p_b
} }
Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) {
print_verbose(vformat("glTF: Importing file %s as scene.", p_path));
GLTFState state; GLTFState state;
if (p_path.to_lower().ends_with("glb")) { if (p_path.to_lower().ends_with("glb")) {

View file

@ -872,7 +872,6 @@ Array ArrayMesh::_get_surfaces() const {
ret.push_back(data); ret.push_back(data);
} }
print_line("Saving surfaces: " + itos(ret.size()));
return ret; return ret;
} }