Skip to main content

bevy_image/
compressed_image_saver.rs

1use crate::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSettings};
2
3use bevy_asset::{
4    saver::{AssetSaver, SavedAsset},
5    AssetPath,
6};
7use bevy_reflect::TypePath;
8use futures_lite::AsyncWriteExt;
9use thiserror::Error;
10
11/// An [`AssetSaver`] that writes compressed basis universal (.ktx2) files.
12#[derive(const _: () =
    {
        #[allow(deprecated, reason =
        "derives on a deprecated type shouldn't be considered a usage")]
        impl bevy_reflect::TypePath for CompressedImageSaver where  {
            fn type_path() -> &'static str {
                "bevy_image::compressed_image_saver::CompressedImageSaver"
            }
            fn short_type_path() -> &'static str { "CompressedImageSaver" }
            fn type_ident() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("CompressedImageSaver")
            }
            fn crate_name() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("bevy_image::compressed_image_saver".split(':').next().unwrap())
            }
            fn module_path() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("bevy_image::compressed_image_saver")
            }
        }
    };TypePath)]
13pub struct CompressedImageSaver;
14
15/// Errors encountered when writing compressed images.
16#[non_exhaustive]
17#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CompressedImageSaverError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            CompressedImageSaverError::Io(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Io",
                    &__self_0),
            CompressedImageSaverError::UninitializedImage =>
                ::core::fmt::Formatter::write_str(f, "UninitializedImage"),
        }
    }
}Debug, fn from(source: std::io::Error) -> Self {
    CompressedImageSaverError::Io { 0: source }
}Error, const _: () =
    {
        #[allow(deprecated, reason =
        "derives on a deprecated type shouldn't be considered a usage")]
        impl bevy_reflect::TypePath for CompressedImageSaverError where  {
            fn type_path() -> &'static str {
                "bevy_image::compressed_image_saver::CompressedImageSaverError"
            }
            fn short_type_path() -> &'static str {
                "CompressedImageSaverError"
            }
            fn type_ident() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("CompressedImageSaverError")
            }
            fn crate_name() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("bevy_image::compressed_image_saver".split(':').next().unwrap())
            }
            fn module_path() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("bevy_image::compressed_image_saver")
            }
        }
    };TypePath)]
18pub enum CompressedImageSaverError {
19    /// I/O error.
20    #[error(transparent)]
21    Io(#[from] std::io::Error),
22    /// Attempted to save an image with uninitialized data.
23    #[error("Cannot compress an uninitialized image")]
24    UninitializedImage,
25}
26
27impl AssetSaver for CompressedImageSaver {
28    type Asset = Image;
29
30    type Settings = ();
31    type OutputLoader = ImageLoader;
32    type Error = CompressedImageSaverError;
33
34    async fn save(
35        &self,
36        writer: &mut bevy_asset::io::Writer,
37        image: SavedAsset<'_, '_, Self::Asset>,
38        _settings: &Self::Settings,
39        _asset_path: AssetPath<'_>,
40    ) -> Result<ImageLoaderSettings, Self::Error> {
41        let is_srgb = image.texture_descriptor.format.is_srgb();
42
43        let compressed_basis_data = {
44            let mut compressor_params = basis_universal::CompressorParams::new();
45            compressor_params.set_basis_format(basis_universal::BasisTextureFormat::UASTC4x4);
46            compressor_params.set_generate_mipmaps(true);
47            let color_space = if is_srgb {
48                basis_universal::ColorSpace::Srgb
49            } else {
50                basis_universal::ColorSpace::Linear
51            };
52            compressor_params.set_color_space(color_space);
53            compressor_params.set_uastc_quality_level(basis_universal::UASTC_QUALITY_DEFAULT);
54
55            let mut source_image = compressor_params.source_image_mut(0);
56            let size = image.size();
57            let Some(ref data) = image.data else {
58                return Err(CompressedImageSaverError::UninitializedImage);
59            };
60            source_image.init(data, size.x, size.y, 4);
61
62            let mut compressor = basis_universal::Compressor::new(4);
63            #[expect(
64                unsafe_code,
65                reason = "The basis-universal compressor cannot be interacted with except through unsafe functions"
66            )]
67            // SAFETY: the CompressorParams are "valid" to the best of our knowledge. The basis-universal
68            // library bindings note that invalid params might produce undefined behavior.
69            unsafe {
70                compressor.init(&compressor_params);
71                compressor.process().unwrap();
72            }
73            compressor.basis_file().to_vec()
74        };
75
76        writer.write_all(&compressed_basis_data).await?;
77        Ok(ImageLoaderSettings {
78            format: ImageFormatSetting::Format(ImageFormat::Basis),
79            is_srgb,
80            sampler: image.sampler.clone(),
81            asset_usage: image.asset_usage,
82            texture_format: None,
83            array_layout: None,
84        })
85    }
86}