#[cfg(feature = "compressed_image_saver")]
mod ctt;
#[cfg(feature = "compressed_image_saver")]
mod ctt_helpers;
#[cfg(all(
feature = "compressed_image_saver_universal",
not(feature = "compressed_image_saver")
))]
mod universal;
#[cfg(feature = "compressed_image_saver")]
use crate::compressed_image_saver::ctt::CompressedImageSaverCtt;
#[cfg(all(
feature = "compressed_image_saver_universal",
not(feature = "compressed_image_saver")
))]
use crate::compressed_image_saver::universal::CompressedImageSaverUniversal;
use crate::{Image, ImageLoader, ImageLoaderSettings};
use bevy_asset::{
io::Writer,
saver::{AssetSaver, SavedAsset},
AssetPath,
};
use bevy_reflect::TypePath;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use wgpu_types::TextureFormat;
#[derive(TypePath, Default)]
#[expect(clippy::doc_markdown, reason = "clippy does not like unquoted BCn")]
pub struct CompressedImageSaver {
#[cfg(feature = "compressed_image_saver")]
inner: CompressedImageSaverCtt,
#[cfg(all(
feature = "compressed_image_saver_universal",
not(feature = "compressed_image_saver")
))]
inner: CompressedImageSaverUniversal,
}
impl AssetSaver for CompressedImageSaver {
type Asset = Image;
type Settings = CompressedImageSaverSettings;
type OutputLoader = ImageLoader;
type Error = CompressedImageSaverError;
async fn save(
&self,
writer: &mut Writer,
asset: SavedAsset<'_, '_, Self::Asset>,
settings: &Self::Settings,
asset_path: AssetPath<'_>,
) -> Result<ImageLoaderSettings, Self::Error> {
let is_srgb = asset.texture_descriptor.format.is_srgb();
if settings.is_normal_map && is_srgb {
return Err(CompressedImageSaverError::NormalMapMustBeLinear(
asset.texture_descriptor.format,
));
}
self.inner.save(writer, asset, settings, asset_path).await
}
}
#[derive(Serialize, Deserialize)]
#[serde(default)]
pub struct CompressedImageSaverSettings {
pub is_normal_map: bool,
pub input_alpha_mode: ImageCompressorAlphaMode,
pub output_alpha_mode: ImageCompressorAlphaMode,
pub generate_mipmaps: bool,
}
impl Default for CompressedImageSaverSettings {
fn default() -> Self {
Self {
is_normal_map: false,
input_alpha_mode: ImageCompressorAlphaMode::Straight,
output_alpha_mode: ImageCompressorAlphaMode::Premultiplied,
generate_mipmaps: true,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ImageCompressorAlphaMode {
Straight,
Premultiplied,
Opaque,
}
#[non_exhaustive]
#[derive(Debug, Error, TypePath)]
pub enum CompressedImageSaverError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
CompressionFailed(Box<dyn core::error::Error + Send + Sync>),
#[error("Cannot compress an uninitialized image")]
UninitializedImage,
#[error("Unsupported texture format for compression: {0:?}")]
UnsupportedFormat(TextureFormat),
#[error(
"Cannot compress an sRGB texture ({0:?}) as a normal map; \
the input Image's texture_descriptor.format must be a non-sRGB (linear) variant"
)]
NormalMapMustBeLinear(TextureFormat),
}