@@ -3804,6 +3804,22 @@ float GLTFDocument::get_lossy_quality() const {
38043804 return _lossy_quality;
38053805}
38063806
3807+ void GLTFDocument::set_fallback_image_format (const String &p_fallback_image_format) {
3808+ _fallback_image_format = p_fallback_image_format;
3809+ }
3810+
3811+ String GLTFDocument::get_fallback_image_format () const {
3812+ return _fallback_image_format;
3813+ }
3814+
3815+ void GLTFDocument::set_fallback_image_quality (float p_fallback_image_quality) {
3816+ _fallback_image_quality = p_fallback_image_quality;
3817+ }
3818+
3819+ float GLTFDocument::get_fallback_image_quality () const {
3820+ return _fallback_image_quality;
3821+ }
3822+
38073823Error GLTFDocument::_serialize_images (Ref<GLTFState> p_state) {
38083824 Array images;
38093825 // Check if any extension wants to be the image saver.
@@ -3819,83 +3835,21 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
38193835 // Serialize every image in the state's images array.
38203836 for (int i = 0 ; i < p_state->images .size (); i++) {
38213837 Dictionary image_dict;
3822-
3823- ERR_CONTINUE (p_state->images [i].is_null ());
3824-
3825- Ref<Image> image = p_state->images [i]->get_image ();
3826- ERR_CONTINUE (image.is_null ());
3827- if (image->is_compressed ()) {
3828- image->decompress ();
3829- ERR_FAIL_COND_V_MSG (image->is_compressed (), ERR_INVALID_DATA, " glTF: Image was compressed, but could not be decompressed." );
3830- }
3831-
3832- if (p_state->filename .to_lower ().ends_with (" gltf" )) {
3833- String img_name = p_state->images [i]->get_name ();
3834- if (img_name.is_empty ()) {
3835- img_name = itos (i).pad_zeros (3 );
3836- }
3837- img_name = _gen_unique_name (p_state, img_name);
3838- String relative_texture_dir = " textures" ;
3839- String full_texture_dir = p_state->base_path .path_join (relative_texture_dir);
3840- Ref<DirAccess> da = DirAccess::open (p_state->base_path );
3841- ERR_FAIL_COND_V (da.is_null (), FAILED);
3842-
3843- if (!da->dir_exists (full_texture_dir)) {
3844- da->make_dir (full_texture_dir);
3845- }
3846- if (_image_save_extension.is_valid ()) {
3847- img_name = img_name + _image_save_extension->get_image_file_extension ();
3848- Error err = _image_save_extension->save_image_at_path (p_state, image, full_texture_dir.path_join (img_name), _image_format, _lossy_quality);
3849- ERR_FAIL_COND_V_MSG (err != OK, err, " glTF: Failed to save image in '" + _image_format + " ' format as a separate file." );
3850- } else if (_image_format == " PNG" ) {
3851- img_name = img_name + " .png" ;
3852- image->save_png (full_texture_dir.path_join (img_name));
3853- } else if (_image_format == " JPEG" ) {
3854- img_name = img_name + " .jpg" ;
3855- image->save_jpg (full_texture_dir.path_join (img_name), _lossy_quality);
3856- } else {
3857- ERR_FAIL_V_MSG (ERR_UNAVAILABLE, " glTF: Unknown image format '" + _image_format + " '." );
3858- }
3859- image_dict[" uri" ] = relative_texture_dir.path_join (img_name).uri_encode ();
3838+ if (p_state->images [i].is_null ()) {
3839+ ERR_PRINT (" glTF export: Image Texture2D is null." );
38603840 } else {
3861- GLTFBufferViewIndex bvi;
3862-
3863- Ref<GLTFBufferView> bv;
3864- bv.instantiate ();
3865-
3866- const GLTFBufferIndex bi = 0 ;
3867- bv->buffer = bi;
3868- bv->byte_offset = p_state->buffers [bi].size ();
3869- ERR_FAIL_INDEX_V (bi, p_state->buffers .size (), ERR_PARAMETER_RANGE_ERROR);
3870-
3871- Vector<uint8_t > buffer;
3872- Ref<ImageTexture> img_tex = image;
3873- if (img_tex.is_valid ()) {
3874- image = img_tex->get_image ();
3875- }
3876- // Save in various image formats. Note that if the format is "None",
3877- // the state's images will be empty, so this code will not be reached.
3878- if (_image_save_extension.is_valid ()) {
3879- buffer = _image_save_extension->serialize_image_to_bytes (p_state, image, image_dict, _image_format, _lossy_quality);
3880- } else if (_image_format == " PNG" ) {
3881- buffer = image->save_png_to_buffer ();
3882- image_dict[" mimeType" ] = " image/png" ;
3883- } else if (_image_format == " JPEG" ) {
3884- buffer = image->save_jpg_to_buffer (_lossy_quality);
3885- image_dict[" mimeType" ] = " image/jpeg" ;
3841+ Ref<Image> image = p_state->images [i]->get_image ();
3842+ if (image.is_null ()) {
3843+ ERR_PRINT (" glTF export: Image's image is null." );
38863844 } else {
3887- ERR_FAIL_V_MSG (ERR_UNAVAILABLE, " glTF: Unknown image format '" + _image_format + " '." );
3845+ String image_name = p_state->images [i]->get_name ();
3846+ if (image_name.is_empty ()) {
3847+ image_name = itos (i).pad_zeros (3 );
3848+ }
3849+ image_name = _gen_unique_name (p_state, image_name);
3850+ image->set_name (image_name);
3851+ image_dict = _serialize_image (p_state, image, _image_format, _lossy_quality, _image_save_extension);
38883852 }
3889- ERR_FAIL_COND_V_MSG (buffer.is_empty (), ERR_INVALID_DATA, " glTF: Failed to save image in '" + _image_format + " ' format." );
3890-
3891- bv->byte_length = buffer.size ();
3892- p_state->buffers .write [bi].resize (p_state->buffers [bi].size () + bv->byte_length );
3893- memcpy (&p_state->buffers .write [bi].write [bv->byte_offset ], buffer.ptr (), buffer.size ());
3894- ERR_FAIL_COND_V (bv->byte_offset + bv->byte_length > p_state->buffers [bi].size (), ERR_FILE_CORRUPT);
3895-
3896- p_state->buffer_views .push_back (bv);
3897- bvi = p_state->buffer_views .size () - 1 ;
3898- image_dict[" bufferView" ] = bvi;
38993853 }
39003854 images.push_back (image_dict);
39013855 }
@@ -3910,6 +3864,80 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
39103864 return OK;
39113865}
39123866
3867+ 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) {
3868+ Dictionary image_dict;
3869+ if (p_image->is_compressed ()) {
3870+ p_image->decompress ();
3871+ ERR_FAIL_COND_V_MSG (p_image->is_compressed (), image_dict, " glTF: Image was compressed, but could not be decompressed." );
3872+ }
3873+
3874+ if (p_state->filename .to_lower ().ends_with (" gltf" )) {
3875+ String relative_texture_dir = " textures" ;
3876+ String full_texture_dir = p_state->base_path .path_join (relative_texture_dir);
3877+ Ref<DirAccess> da = DirAccess::open (p_state->base_path );
3878+ ERR_FAIL_COND_V (da.is_null (), image_dict);
3879+
3880+ if (!da->dir_exists (full_texture_dir)) {
3881+ da->make_dir (full_texture_dir);
3882+ }
3883+ String image_file_name = p_image->get_name ();
3884+ if (p_image_save_extension.is_valid ()) {
3885+ image_file_name = image_file_name + p_image_save_extension->get_image_file_extension ();
3886+ 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);
3887+ 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) + " ." );
3888+ } else if (p_image_format == " PNG" ) {
3889+ image_file_name = image_file_name + " .png" ;
3890+ p_image->save_png (full_texture_dir.path_join (image_file_name));
3891+ } else if (p_image_format == " JPEG" ) {
3892+ image_file_name = image_file_name + " .jpg" ;
3893+ p_image->save_jpg (full_texture_dir.path_join (image_file_name), p_lossy_quality);
3894+ } else {
3895+ ERR_FAIL_V_MSG (image_dict, " glTF: Unknown image format '" + p_image_format + " '." );
3896+ }
3897+ image_dict[" uri" ] = relative_texture_dir.path_join (image_file_name).uri_encode ();
3898+ } else {
3899+ GLTFBufferViewIndex bvi;
3900+
3901+ Ref<GLTFBufferView> bv;
3902+ bv.instantiate ();
3903+
3904+ const GLTFBufferIndex bi = 0 ;
3905+ bv->buffer = bi;
3906+ ERR_FAIL_INDEX_V (bi, p_state->buffers .size (), image_dict);
3907+ bv->byte_offset = p_state->buffers [bi].size ();
3908+
3909+ Vector<uint8_t > buffer;
3910+ Ref<ImageTexture> img_tex = p_image;
3911+ if (img_tex.is_valid ()) {
3912+ p_image = img_tex->get_image ();
3913+ }
3914+ // Save in various image formats. Note that if the format is "None",
3915+ // the state's images will be empty, so this code will not be reached.
3916+ if (_image_save_extension.is_valid ()) {
3917+ buffer = _image_save_extension->serialize_image_to_bytes (p_state, p_image, image_dict, p_image_format, p_lossy_quality);
3918+ } else if (p_image_format == " PNG" ) {
3919+ buffer = p_image->save_png_to_buffer ();
3920+ image_dict[" mimeType" ] = " image/png" ;
3921+ } else if (p_image_format == " JPEG" ) {
3922+ buffer = p_image->save_jpg_to_buffer (p_lossy_quality);
3923+ image_dict[" mimeType" ] = " image/jpeg" ;
3924+ } else {
3925+ ERR_FAIL_V_MSG (image_dict, " glTF: Unknown image format '" + p_image_format + " '." );
3926+ }
3927+ ERR_FAIL_COND_V_MSG (buffer.is_empty (), image_dict, " glTF: Failed to save image in '" + p_image_format + " ' format." );
3928+
3929+ bv->byte_length = buffer.size ();
3930+ p_state->buffers .write [bi].resize (p_state->buffers [bi].size () + bv->byte_length );
3931+ memcpy (&p_state->buffers .write [bi].write [bv->byte_offset ], buffer.ptr (), buffer.size ());
3932+ ERR_FAIL_COND_V (bv->byte_offset + bv->byte_length > p_state->buffers [bi].size (), image_dict);
3933+
3934+ p_state->buffer_views .push_back (bv);
3935+ bvi = p_state->buffer_views .size () - 1 ;
3936+ image_dict[" bufferView" ] = bvi;
3937+ }
3938+ return image_dict;
3939+ }
3940+
39133941Ref<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) {
39143942 Ref<Image> r_image;
39153943 r_image.instantiate ();
@@ -4199,6 +4227,21 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> p_state) {
41994227 if (_image_save_extension.is_valid ()) {
42004228 Error err = _image_save_extension->serialize_texture_json (p_state, texture_dict, gltf_texture, _image_format);
42014229 ERR_FAIL_COND_V (err != OK, err);
4230+ // If a fallback image format was specified, serialize another image for it.
4231+ // Note: This must only be done after serializing other images to keep the indices of those consistent.
4232+ if (_fallback_image_format != " None" && p_state->json .has (" images" )) {
4233+ Array json_images = p_state->json [" images" ];
4234+ texture_dict[" source" ] = json_images.size ();
4235+ Ref<Image> image = p_state->source_images [gltf_texture->get_src_image ()];
4236+ String fallback_name = _gen_unique_name (p_state, image->get_name () + " _fallback" );
4237+ image = image->duplicate ();
4238+ image->set_name (fallback_name);
4239+ ERR_CONTINUE (image.is_null ());
4240+ if (_fallback_image_format == " PNG" ) {
4241+ image->resize (image->get_width () * _fallback_image_quality, image->get_height () * _fallback_image_quality);
4242+ }
4243+ json_images.push_back (_serialize_image (p_state, image, _fallback_image_format, _fallback_image_quality, nullptr ));
4244+ }
42024245 } else {
42034246 ERR_CONTINUE (gltf_texture->get_src_image () == -1 );
42044247 texture_dict[" source" ] = gltf_texture->get_src_image ();
@@ -8159,6 +8202,10 @@ void GLTFDocument::_bind_methods() {
81598202 ClassDB::bind_method (D_METHOD (" get_image_format" ), &GLTFDocument::get_image_format);
81608203 ClassDB::bind_method (D_METHOD (" set_lossy_quality" , " lossy_quality" ), &GLTFDocument::set_lossy_quality);
81618204 ClassDB::bind_method (D_METHOD (" get_lossy_quality" ), &GLTFDocument::get_lossy_quality);
8205+ ClassDB::bind_method (D_METHOD (" set_fallback_image_format" , " fallback_image_format" ), &GLTFDocument::set_fallback_image_format);
8206+ ClassDB::bind_method (D_METHOD (" get_fallback_image_format" ), &GLTFDocument::get_fallback_image_format);
8207+ ClassDB::bind_method (D_METHOD (" set_fallback_image_quality" , " fallback_image_quality" ), &GLTFDocument::set_fallback_image_quality);
8208+ ClassDB::bind_method (D_METHOD (" get_fallback_image_quality" ), &GLTFDocument::get_fallback_image_quality);
81628209 ClassDB::bind_method (D_METHOD (" set_root_node_mode" , " root_node_mode" ), &GLTFDocument::set_root_node_mode);
81638210 ClassDB::bind_method (D_METHOD (" get_root_node_mode" ), &GLTFDocument::get_root_node_mode);
81648211 ClassDB::bind_method (D_METHOD (" append_from_file" , " path" , " state" , " flags" , " base_path" ),
@@ -8176,6 +8223,8 @@ void GLTFDocument::_bind_methods() {
81768223
81778224 ADD_PROPERTY (PropertyInfo (Variant::STRING, " image_format" ), " set_image_format" , " get_image_format" );
81788225 ADD_PROPERTY (PropertyInfo (Variant::FLOAT, " lossy_quality" ), " set_lossy_quality" , " get_lossy_quality" );
8226+ ADD_PROPERTY (PropertyInfo (Variant::STRING, " fallback_image_format" ), " set_fallback_image_format" , " get_fallback_image_format" );
8227+ ADD_PROPERTY (PropertyInfo (Variant::FLOAT, " fallback_image_quality" ), " set_fallback_image_quality" , " get_fallback_image_quality" );
81798228 ADD_PROPERTY (PropertyInfo (Variant::INT, " root_node_mode" ), " set_root_node_mode" , " get_root_node_mode" );
81808229
81818230 ClassDB::bind_static_method (" GLTFDocument" , D_METHOD (" import_object_model_property" , " state" , " json_pointer" ), &GLTFDocument::import_object_model_property);
0 commit comments