@@ -3814,6 +3814,22 @@ float GLTFDocument::get_lossy_quality() const {
38143814 return _lossy_quality;
38153815}
38163816
3817+ void GLTFDocument::set_fallback_image_format (const String &p_fallback_image_format) {
3818+ _fallback_image_format = p_fallback_image_format;
3819+ }
3820+
3821+ String GLTFDocument::get_fallback_image_format () const {
3822+ return _fallback_image_format;
3823+ }
3824+
3825+ void GLTFDocument::set_fallback_image_quality (float p_fallback_image_quality) {
3826+ _fallback_image_quality = p_fallback_image_quality;
3827+ }
3828+
3829+ float GLTFDocument::get_fallback_image_quality () const {
3830+ return _fallback_image_quality;
3831+ }
3832+
38173833Error GLTFDocument::_serialize_images (Ref<GLTFState> p_state) {
38183834 Array images;
38193835 // Check if any extension wants to be the image saver.
@@ -3829,83 +3845,21 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
38293845 // Serialize every image in the state's images array.
38303846 for (int i = 0 ; i < p_state->images .size (); i++) {
38313847 Dictionary image_dict;
3832-
3833- ERR_CONTINUE (p_state->images [i].is_null ());
3834-
3835- Ref<Image> image = p_state->images [i]->get_image ();
3836- ERR_CONTINUE (image.is_null ());
3837- if (image->is_compressed ()) {
3838- image->decompress ();
3839- ERR_FAIL_COND_V_MSG (image->is_compressed (), ERR_INVALID_DATA, " glTF: Image was compressed, but could not be decompressed." );
3840- }
3841-
3842- if (p_state->filename .to_lower ().ends_with (" gltf" )) {
3843- String img_name = p_state->images [i]->get_name ();
3844- if (img_name.is_empty ()) {
3845- img_name = itos (i).pad_zeros (3 );
3846- }
3847- img_name = _gen_unique_name (p_state, img_name);
3848- String relative_texture_dir = " textures" ;
3849- String full_texture_dir = p_state->base_path .path_join (relative_texture_dir);
3850- Ref<DirAccess> da = DirAccess::open (p_state->base_path );
3851- ERR_FAIL_COND_V (da.is_null (), FAILED);
3852-
3853- if (!da->dir_exists (full_texture_dir)) {
3854- da->make_dir (full_texture_dir);
3855- }
3856- if (_image_save_extension.is_valid ()) {
3857- img_name = img_name + _image_save_extension->get_image_file_extension ();
3858- Error err = _image_save_extension->save_image_at_path (p_state, image, full_texture_dir.path_join (img_name), _image_format, _lossy_quality);
3859- ERR_FAIL_COND_V_MSG (err != OK, err, " glTF: Failed to save image in '" + _image_format + " ' format as a separate file." );
3860- } else if (_image_format == " PNG" ) {
3861- img_name = img_name + " .png" ;
3862- image->save_png (full_texture_dir.path_join (img_name));
3863- } else if (_image_format == " JPEG" ) {
3864- img_name = img_name + " .jpg" ;
3865- image->save_jpg (full_texture_dir.path_join (img_name), _lossy_quality);
3866- } else {
3867- ERR_FAIL_V_MSG (ERR_UNAVAILABLE, " glTF: Unknown image format '" + _image_format + " '." );
3868- }
3869- image_dict[" uri" ] = relative_texture_dir.path_join (img_name).uri_encode ();
3848+ if (p_state->images [i].is_null ()) {
3849+ ERR_PRINT (" glTF export: Image Texture2D is null." );
38703850 } else {
3871- GLTFBufferViewIndex bvi;
3872-
3873- Ref<GLTFBufferView> bv;
3874- bv.instantiate ();
3875-
3876- const GLTFBufferIndex bi = 0 ;
3877- bv->buffer = bi;
3878- bv->byte_offset = p_state->buffers [bi].size ();
3879- ERR_FAIL_INDEX_V (bi, p_state->buffers .size (), ERR_PARAMETER_RANGE_ERROR);
3880-
3881- Vector<uint8_t > buffer;
3882- Ref<ImageTexture> img_tex = image;
3883- if (img_tex.is_valid ()) {
3884- image = img_tex->get_image ();
3885- }
3886- // Save in various image formats. Note that if the format is "None",
3887- // the state's images will be empty, so this code will not be reached.
3888- if (_image_save_extension.is_valid ()) {
3889- buffer = _image_save_extension->serialize_image_to_bytes (p_state, image, image_dict, _image_format, _lossy_quality);
3890- } else if (_image_format == " PNG" ) {
3891- buffer = image->save_png_to_buffer ();
3892- image_dict[" mimeType" ] = " image/png" ;
3893- } else if (_image_format == " JPEG" ) {
3894- buffer = image->save_jpg_to_buffer (_lossy_quality);
3895- image_dict[" mimeType" ] = " image/jpeg" ;
3851+ Ref<Image> image = p_state->images [i]->get_image ();
3852+ if (image.is_null ()) {
3853+ ERR_PRINT (" glTF export: Image's image is null." );
38963854 } else {
3897- ERR_FAIL_V_MSG (ERR_UNAVAILABLE, " glTF: Unknown image format '" + _image_format + " '." );
3855+ String image_name = p_state->images [i]->get_name ();
3856+ if (image_name.is_empty ()) {
3857+ image_name = itos (i).pad_zeros (3 );
3858+ }
3859+ image_name = _gen_unique_name (p_state, image_name);
3860+ image->set_name (image_name);
3861+ image_dict = _serialize_image (p_state, image, _image_format, _lossy_quality, _image_save_extension);
38983862 }
3899- ERR_FAIL_COND_V_MSG (buffer.is_empty (), ERR_INVALID_DATA, " glTF: Failed to save image in '" + _image_format + " ' format." );
3900-
3901- bv->byte_length = buffer.size ();
3902- p_state->buffers .write [bi].resize (p_state->buffers [bi].size () + bv->byte_length );
3903- memcpy (&p_state->buffers .write [bi].write [bv->byte_offset ], buffer.ptr (), buffer.size ());
3904- ERR_FAIL_COND_V (bv->byte_offset + bv->byte_length > p_state->buffers [bi].size (), ERR_FILE_CORRUPT);
3905-
3906- p_state->buffer_views .push_back (bv);
3907- bvi = p_state->buffer_views .size () - 1 ;
3908- image_dict[" bufferView" ] = bvi;
39093863 }
39103864 images.push_back (image_dict);
39113865 }
@@ -3920,6 +3874,80 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
39203874 return OK;
39213875}
39223876
3877+ Dictionary GLTFDocument::_serialize_image (Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_image_format, float p_lossy_quality, Ref<GLTFDocumentExtension> p_image_save_extension) {
3878+ Dictionary image_dict;
3879+ if (p_image->is_compressed ()) {
3880+ p_image->decompress ();
3881+ ERR_FAIL_COND_V_MSG (p_image->is_compressed (), image_dict, " glTF: Image was compressed, but could not be decompressed." );
3882+ }
3883+
3884+ if (p_state->filename .to_lower ().ends_with (" gltf" )) {
3885+ String relative_texture_dir = " textures" ;
3886+ String full_texture_dir = p_state->base_path .path_join (relative_texture_dir);
3887+ Ref<DirAccess> da = DirAccess::open (p_state->base_path );
3888+ ERR_FAIL_COND_V (da.is_null (), image_dict);
3889+
3890+ if (!da->dir_exists (full_texture_dir)) {
3891+ da->make_dir (full_texture_dir);
3892+ }
3893+ String image_file_name = p_image->get_name ();
3894+ if (p_image_save_extension.is_valid ()) {
3895+ image_file_name = image_file_name + p_image_save_extension->get_image_file_extension ();
3896+ Error err = p_image_save_extension->save_image_at_path (p_state, p_image, full_texture_dir.path_join (image_file_name), p_image_format, p_lossy_quality);
3897+ ERR_FAIL_COND_V_MSG (err != OK, image_dict, " glTF: Failed to save image in '" + p_image_format + " ' format as a separate file, error " + itos (err) + " ." );
3898+ } else if (p_image_format == " PNG" ) {
3899+ image_file_name = image_file_name + " .png" ;
3900+ p_image->save_png (full_texture_dir.path_join (image_file_name));
3901+ } else if (p_image_format == " JPEG" ) {
3902+ image_file_name = image_file_name + " .jpg" ;
3903+ p_image->save_jpg (full_texture_dir.path_join (image_file_name), p_lossy_quality);
3904+ } else {
3905+ ERR_FAIL_V_MSG (image_dict, " glTF: Unknown image format '" + p_image_format + " '." );
3906+ }
3907+ image_dict[" uri" ] = relative_texture_dir.path_join (image_file_name).uri_encode ();
3908+ } else {
3909+ GLTFBufferViewIndex bvi;
3910+
3911+ Ref<GLTFBufferView> bv;
3912+ bv.instantiate ();
3913+
3914+ const GLTFBufferIndex bi = 0 ;
3915+ bv->buffer = bi;
3916+ ERR_FAIL_INDEX_V (bi, p_state->buffers .size (), image_dict);
3917+ bv->byte_offset = p_state->buffers [bi].size ();
3918+
3919+ Vector<uint8_t > buffer;
3920+ Ref<ImageTexture> img_tex = p_image;
3921+ if (img_tex.is_valid ()) {
3922+ p_image = img_tex->get_image ();
3923+ }
3924+ // Save in various image formats. Note that if the format is "None",
3925+ // the state's images will be empty, so this code will not be reached.
3926+ if (_image_save_extension.is_valid ()) {
3927+ buffer = _image_save_extension->serialize_image_to_bytes (p_state, p_image, image_dict, p_image_format, p_lossy_quality);
3928+ } else if (p_image_format == " PNG" ) {
3929+ buffer = p_image->save_png_to_buffer ();
3930+ image_dict[" mimeType" ] = " image/png" ;
3931+ } else if (p_image_format == " JPEG" ) {
3932+ buffer = p_image->save_jpg_to_buffer (p_lossy_quality);
3933+ image_dict[" mimeType" ] = " image/jpeg" ;
3934+ } else {
3935+ ERR_FAIL_V_MSG (image_dict, " glTF: Unknown image format '" + p_image_format + " '." );
3936+ }
3937+ ERR_FAIL_COND_V_MSG (buffer.is_empty (), image_dict, " glTF: Failed to save image in '" + p_image_format + " ' format." );
3938+
3939+ bv->byte_length = buffer.size ();
3940+ p_state->buffers .write [bi].resize (p_state->buffers [bi].size () + bv->byte_length );
3941+ memcpy (&p_state->buffers .write [bi].write [bv->byte_offset ], buffer.ptr (), buffer.size ());
3942+ ERR_FAIL_COND_V (bv->byte_offset + bv->byte_length > p_state->buffers [bi].size (), image_dict);
3943+
3944+ p_state->buffer_views .push_back (bv);
3945+ bvi = p_state->buffer_views .size () - 1 ;
3946+ image_dict[" bufferView" ] = bvi;
3947+ }
3948+ return image_dict;
3949+ }
3950+
39233951Ref<Image> GLTFDocument::_parse_image_bytes_into_image (Ref<GLTFState> p_state, const Vector<uint8_t > &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension) {
39243952 Ref<Image> r_image;
39253953 r_image.instantiate ();
@@ -4209,6 +4237,21 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> p_state) {
42094237 if (_image_save_extension.is_valid ()) {
42104238 Error err = _image_save_extension->serialize_texture_json (p_state, texture_dict, gltf_texture, _image_format);
42114239 ERR_FAIL_COND_V (err != OK, err);
4240+ // If a fallback image format was specified, serialize another image for it.
4241+ // Note: This must only be done after serializing other images to keep the indices of those consistent.
4242+ if (_fallback_image_format != " None" && p_state->json .has (" images" )) {
4243+ Array json_images = p_state->json [" images" ];
4244+ texture_dict[" source" ] = json_images.size ();
4245+ Ref<Image> image = p_state->source_images [gltf_texture->get_src_image ()];
4246+ String fallback_name = _gen_unique_name (p_state, image->get_name () + " _fallback" );
4247+ image = image->duplicate ();
4248+ image->set_name (fallback_name);
4249+ ERR_CONTINUE (image.is_null ());
4250+ if (_fallback_image_format == " PNG" ) {
4251+ image->resize (image->get_width () * _fallback_image_quality, image->get_height () * _fallback_image_quality);
4252+ }
4253+ json_images.push_back (_serialize_image (p_state, image, _fallback_image_format, _fallback_image_quality, nullptr ));
4254+ }
42124255 } else {
42134256 ERR_CONTINUE (gltf_texture->get_src_image () == -1 );
42144257 texture_dict[" source" ] = gltf_texture->get_src_image ();
@@ -8169,6 +8212,10 @@ void GLTFDocument::_bind_methods() {
81698212 ClassDB::bind_method (D_METHOD (" get_image_format" ), &GLTFDocument::get_image_format);
81708213 ClassDB::bind_method (D_METHOD (" set_lossy_quality" , " lossy_quality" ), &GLTFDocument::set_lossy_quality);
81718214 ClassDB::bind_method (D_METHOD (" get_lossy_quality" ), &GLTFDocument::get_lossy_quality);
8215+ ClassDB::bind_method (D_METHOD (" set_fallback_image_format" , " fallback_image_format" ), &GLTFDocument::set_fallback_image_format);
8216+ ClassDB::bind_method (D_METHOD (" get_fallback_image_format" ), &GLTFDocument::get_fallback_image_format);
8217+ ClassDB::bind_method (D_METHOD (" set_fallback_image_quality" , " fallback_image_quality" ), &GLTFDocument::set_fallback_image_quality);
8218+ ClassDB::bind_method (D_METHOD (" get_fallback_image_quality" ), &GLTFDocument::get_fallback_image_quality);
81728219 ClassDB::bind_method (D_METHOD (" set_root_node_mode" , " root_node_mode" ), &GLTFDocument::set_root_node_mode);
81738220 ClassDB::bind_method (D_METHOD (" get_root_node_mode" ), &GLTFDocument::get_root_node_mode);
81748221 ClassDB::bind_method (D_METHOD (" append_from_file" , " path" , " state" , " flags" , " base_path" ),
@@ -8186,6 +8233,8 @@ void GLTFDocument::_bind_methods() {
81868233
81878234 ADD_PROPERTY (PropertyInfo (Variant::STRING, " image_format" ), " set_image_format" , " get_image_format" );
81888235 ADD_PROPERTY (PropertyInfo (Variant::FLOAT, " lossy_quality" ), " set_lossy_quality" , " get_lossy_quality" );
8236+ ADD_PROPERTY (PropertyInfo (Variant::STRING, " fallback_image_format" ), " set_fallback_image_format" , " get_fallback_image_format" );
8237+ ADD_PROPERTY (PropertyInfo (Variant::FLOAT, " fallback_image_quality" ), " set_fallback_image_quality" , " get_fallback_image_quality" );
81898238 ADD_PROPERTY (PropertyInfo (Variant::INT, " root_node_mode" ), " set_root_node_mode" , " get_root_node_mode" );
81908239
81918240 ClassDB::bind_static_method (" GLTFDocument" , D_METHOD (" import_object_model_property" , " state" , " json_pointer" ), &GLTFDocument::import_object_model_property);
0 commit comments