bevy_image/
image.rs

1use crate::ImageLoader;
2
3#[cfg(feature = "basis-universal")]
4use super::basis::*;
5#[cfg(feature = "dds")]
6use super::dds::*;
7#[cfg(feature = "ktx2")]
8use super::ktx2::*;
9use bevy_app::{App, Plugin};
10#[cfg(not(feature = "bevy_reflect"))]
11use bevy_reflect::TypePath;
12#[cfg(feature = "bevy_reflect")]
13use bevy_reflect::{std_traits::ReflectDefault, Reflect};
14
15use bevy_asset::{uuid_handle, Asset, AssetApp, Assets, Handle, RenderAssetUsages};
16use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza};
17use bevy_ecs::resource::Resource;
18use bevy_math::{AspectRatio, UVec2, UVec3, Vec2};
19use core::hash::Hash;
20use serde::{Deserialize, Serialize};
21use thiserror::Error;
22use wgpu_types::{
23    AddressMode, CompareFunction, Extent3d, Features, FilterMode, SamplerBorderColor,
24    SamplerDescriptor, TextureDataOrder, TextureDescriptor, TextureDimension, TextureFormat,
25    TextureUsages, TextureViewDescriptor,
26};
27
28/// Trait used to provide default values for Bevy-external types that
29/// do not implement [`Default`].
30pub trait BevyDefault {
31    /// Returns the default value for a type.
32    fn bevy_default() -> Self;
33}
34
35impl BevyDefault for TextureFormat {
36    fn bevy_default() -> Self {
37        TextureFormat::Rgba8UnormSrgb
38    }
39}
40
41/// A handle to a 1 x 1 transparent white image.
42///
43/// Like [`Handle<Image>::default`], this is a handle to a fallback image asset.
44/// While that handle points to an opaque white 1 x 1 image, this handle points to a transparent 1 x 1 white image.
45// Number randomly selected by fair WolframAlpha query. Totally arbitrary.
46pub const TRANSPARENT_IMAGE_HANDLE: Handle<Image> =
47    uuid_handle!("d18ad97e-a322-4981-9505-44c59a4b5e46");
48
49/// Adds the [`Image`] as an asset and makes sure that they are extracted and prepared for the GPU.
50pub struct ImagePlugin {
51    /// The default image sampler to use when [`ImageSampler`] is set to `Default`.
52    pub default_sampler: ImageSamplerDescriptor,
53}
54
55impl Default for ImagePlugin {
56    fn default() -> Self {
57        ImagePlugin::default_linear()
58    }
59}
60
61impl ImagePlugin {
62    /// Creates image settings with linear sampling by default.
63    pub fn default_linear() -> ImagePlugin {
64        ImagePlugin {
65            default_sampler: ImageSamplerDescriptor::linear(),
66        }
67    }
68
69    /// Creates image settings with nearest sampling by default.
70    pub fn default_nearest() -> ImagePlugin {
71        ImagePlugin {
72            default_sampler: ImageSamplerDescriptor::nearest(),
73        }
74    }
75}
76
77impl Plugin for ImagePlugin {
78    fn build(&self, app: &mut App) {
79        #[cfg(feature = "exr")]
80        app.init_asset_loader::<crate::ExrTextureLoader>();
81
82        #[cfg(feature = "hdr")]
83        app.init_asset_loader::<crate::HdrTextureLoader>();
84
85        app.init_asset::<Image>();
86        #[cfg(feature = "bevy_reflect")]
87        app.register_asset_reflect::<Image>();
88
89        let mut image_assets = app.world_mut().resource_mut::<Assets<Image>>();
90
91        image_assets
92            .insert(&Handle::default(), Image::default())
93            .unwrap();
94        image_assets
95            .insert(&TRANSPARENT_IMAGE_HANDLE, Image::transparent())
96            .unwrap();
97
98        #[cfg(feature = "compressed_image_saver")]
99        if let Some(processor) = app
100            .world()
101            .get_resource::<bevy_asset::processor::AssetProcessor>()
102        {
103            processor.register_processor::<bevy_asset::processor::LoadTransformAndSave<
104                ImageLoader,
105                bevy_asset::transformer::IdentityAssetTransformer<Image>,
106                crate::CompressedImageSaver,
107            >>(crate::CompressedImageSaver.into());
108            processor.set_default_processor::<bevy_asset::processor::LoadTransformAndSave<
109                ImageLoader,
110                bevy_asset::transformer::IdentityAssetTransformer<Image>,
111                crate::CompressedImageSaver,
112            >>("png");
113        }
114
115        app.preregister_asset_loader::<ImageLoader>(ImageLoader::SUPPORTED_FILE_EXTENSIONS);
116    }
117}
118
119pub const TEXTURE_ASSET_INDEX: u64 = 0;
120pub const SAMPLER_ASSET_INDEX: u64 = 1;
121
122#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
123pub enum ImageFormat {
124    #[cfg(feature = "basis-universal")]
125    Basis,
126    #[cfg(feature = "bmp")]
127    Bmp,
128    #[cfg(feature = "dds")]
129    Dds,
130    #[cfg(feature = "ff")]
131    Farbfeld,
132    #[cfg(feature = "gif")]
133    Gif,
134    #[cfg(feature = "exr")]
135    OpenExr,
136    #[cfg(feature = "hdr")]
137    Hdr,
138    #[cfg(feature = "ico")]
139    Ico,
140    #[cfg(feature = "jpeg")]
141    Jpeg,
142    #[cfg(feature = "ktx2")]
143    Ktx2,
144    #[cfg(feature = "png")]
145    Png,
146    #[cfg(feature = "pnm")]
147    Pnm,
148    #[cfg(feature = "qoi")]
149    Qoi,
150    #[cfg(feature = "tga")]
151    Tga,
152    #[cfg(feature = "tiff")]
153    Tiff,
154    #[cfg(feature = "webp")]
155    WebP,
156}
157
158macro_rules! feature_gate {
159    ($feature: tt, $value: ident) => {{
160        #[cfg(not(feature = $feature))]
161        {
162            tracing::warn!("feature \"{}\" is not enabled", $feature);
163            return None;
164        }
165        #[cfg(feature = $feature)]
166        ImageFormat::$value
167    }};
168}
169
170impl ImageFormat {
171    /// Gets the file extensions for a given format.
172    pub const fn to_file_extensions(&self) -> &'static [&'static str] {
173        match self {
174            #[cfg(feature = "basis-universal")]
175            ImageFormat::Basis => &["basis"],
176            #[cfg(feature = "bmp")]
177            ImageFormat::Bmp => &["bmp"],
178            #[cfg(feature = "dds")]
179            ImageFormat::Dds => &["dds"],
180            #[cfg(feature = "ff")]
181            ImageFormat::Farbfeld => &["ff", "farbfeld"],
182            #[cfg(feature = "gif")]
183            ImageFormat::Gif => &["gif"],
184            #[cfg(feature = "exr")]
185            ImageFormat::OpenExr => &["exr"],
186            #[cfg(feature = "hdr")]
187            ImageFormat::Hdr => &["hdr"],
188            #[cfg(feature = "ico")]
189            ImageFormat::Ico => &["ico"],
190            #[cfg(feature = "jpeg")]
191            ImageFormat::Jpeg => &["jpg", "jpeg"],
192            #[cfg(feature = "ktx2")]
193            ImageFormat::Ktx2 => &["ktx2"],
194            #[cfg(feature = "pnm")]
195            ImageFormat::Pnm => &["pam", "pbm", "pgm", "ppm"],
196            #[cfg(feature = "png")]
197            ImageFormat::Png => &["png"],
198            #[cfg(feature = "qoi")]
199            ImageFormat::Qoi => &["qoi"],
200            #[cfg(feature = "tga")]
201            ImageFormat::Tga => &["tga"],
202            #[cfg(feature = "tiff")]
203            ImageFormat::Tiff => &["tif", "tiff"],
204            #[cfg(feature = "webp")]
205            ImageFormat::WebP => &["webp"],
206            // FIXME: https://github.com/rust-lang/rust/issues/129031
207            #[expect(
208                clippy::allow_attributes,
209                reason = "`unreachable_patterns` may not always lint"
210            )]
211            #[allow(
212                unreachable_patterns,
213                reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
214            )]
215            _ => &[],
216        }
217    }
218
219    /// Gets the MIME types for a given format.
220    ///
221    /// If a format doesn't have any dedicated MIME types, this list will be empty.
222    pub const fn to_mime_types(&self) -> &'static [&'static str] {
223        match self {
224            #[cfg(feature = "basis-universal")]
225            ImageFormat::Basis => &["image/basis", "image/x-basis"],
226            #[cfg(feature = "bmp")]
227            ImageFormat::Bmp => &["image/bmp", "image/x-bmp"],
228            #[cfg(feature = "dds")]
229            ImageFormat::Dds => &["image/vnd-ms.dds"],
230            #[cfg(feature = "hdr")]
231            ImageFormat::Hdr => &["image/vnd.radiance"],
232            #[cfg(feature = "gif")]
233            ImageFormat::Gif => &["image/gif"],
234            #[cfg(feature = "ff")]
235            ImageFormat::Farbfeld => &[],
236            #[cfg(feature = "ico")]
237            ImageFormat::Ico => &["image/x-icon"],
238            #[cfg(feature = "jpeg")]
239            ImageFormat::Jpeg => &["image/jpeg"],
240            #[cfg(feature = "ktx2")]
241            ImageFormat::Ktx2 => &["image/ktx2"],
242            #[cfg(feature = "png")]
243            ImageFormat::Png => &["image/png"],
244            #[cfg(feature = "qoi")]
245            ImageFormat::Qoi => &["image/qoi", "image/x-qoi"],
246            #[cfg(feature = "exr")]
247            ImageFormat::OpenExr => &["image/x-exr"],
248            #[cfg(feature = "pnm")]
249            ImageFormat::Pnm => &[
250                "image/x-portable-bitmap",
251                "image/x-portable-graymap",
252                "image/x-portable-pixmap",
253                "image/x-portable-anymap",
254            ],
255            #[cfg(feature = "tga")]
256            ImageFormat::Tga => &["image/x-targa", "image/x-tga"],
257            #[cfg(feature = "tiff")]
258            ImageFormat::Tiff => &["image/tiff"],
259            #[cfg(feature = "webp")]
260            ImageFormat::WebP => &["image/webp"],
261            // FIXME: https://github.com/rust-lang/rust/issues/129031
262            #[expect(
263                clippy::allow_attributes,
264                reason = "`unreachable_patterns` may not always lint"
265            )]
266            #[allow(
267                unreachable_patterns,
268                reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
269            )]
270            _ => &[],
271        }
272    }
273
274    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
275        #[expect(
276            clippy::allow_attributes,
277            reason = "`unreachable_code` may not always lint"
278        )]
279        #[allow(
280            unreachable_code,
281            reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
282        )]
283        Some(match mime_type.to_ascii_lowercase().as_str() {
284            // note: farbfeld does not have a MIME type
285            "image/basis" | "image/x-basis" => feature_gate!("basis-universal", Basis),
286            "image/bmp" | "image/x-bmp" => feature_gate!("bmp", Bmp),
287            "image/vnd-ms.dds" => feature_gate!("dds", Dds),
288            "image/vnd.radiance" => feature_gate!("hdr", Hdr),
289            "image/gif" => feature_gate!("gif", Gif),
290            "image/x-icon" => feature_gate!("ico", Ico),
291            "image/jpeg" => feature_gate!("jpeg", Jpeg),
292            "image/ktx2" => feature_gate!("ktx2", Ktx2),
293            "image/png" => feature_gate!("png", Png),
294            "image/qoi" | "image/x-qoi" => feature_gate!("qoi", Qoi),
295            "image/x-exr" => feature_gate!("exr", OpenExr),
296            "image/x-portable-bitmap"
297            | "image/x-portable-graymap"
298            | "image/x-portable-pixmap"
299            | "image/x-portable-anymap" => feature_gate!("pnm", Pnm),
300            "image/x-targa" | "image/x-tga" => feature_gate!("tga", Tga),
301            "image/tiff" => feature_gate!("tiff", Tiff),
302            "image/webp" => feature_gate!("webp", WebP),
303            _ => return None,
304        })
305    }
306
307    pub fn from_extension(extension: &str) -> Option<Self> {
308        #[expect(
309            clippy::allow_attributes,
310            reason = "`unreachable_code` may not always lint"
311        )]
312        #[allow(
313            unreachable_code,
314            reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
315        )]
316        Some(match extension.to_ascii_lowercase().as_str() {
317            "basis" => feature_gate!("basis-universal", Basis),
318            "bmp" => feature_gate!("bmp", Bmp),
319            "dds" => feature_gate!("dds", Dds),
320            "ff" | "farbfeld" => feature_gate!("ff", Farbfeld),
321            "gif" => feature_gate!("gif", Gif),
322            "exr" => feature_gate!("exr", OpenExr),
323            "hdr" => feature_gate!("hdr", Hdr),
324            "ico" => feature_gate!("ico", Ico),
325            "jpg" | "jpeg" => feature_gate!("jpeg", Jpeg),
326            "ktx2" => feature_gate!("ktx2", Ktx2),
327            "pam" | "pbm" | "pgm" | "ppm" => feature_gate!("pnm", Pnm),
328            "png" => feature_gate!("png", Png),
329            "qoi" => feature_gate!("qoi", Qoi),
330            "tga" => feature_gate!("tga", Tga),
331            "tif" | "tiff" => feature_gate!("tiff", Tiff),
332            "webp" => feature_gate!("webp", WebP),
333            _ => return None,
334        })
335    }
336
337    pub fn as_image_crate_format(&self) -> Option<image::ImageFormat> {
338        #[expect(
339            clippy::allow_attributes,
340            reason = "`unreachable_code` may not always lint"
341        )]
342        #[allow(
343            unreachable_code,
344            reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
345        )]
346        Some(match self {
347            #[cfg(feature = "bmp")]
348            ImageFormat::Bmp => image::ImageFormat::Bmp,
349            #[cfg(feature = "dds")]
350            ImageFormat::Dds => image::ImageFormat::Dds,
351            #[cfg(feature = "ff")]
352            ImageFormat::Farbfeld => image::ImageFormat::Farbfeld,
353            #[cfg(feature = "gif")]
354            ImageFormat::Gif => image::ImageFormat::Gif,
355            #[cfg(feature = "exr")]
356            ImageFormat::OpenExr => image::ImageFormat::OpenExr,
357            #[cfg(feature = "hdr")]
358            ImageFormat::Hdr => image::ImageFormat::Hdr,
359            #[cfg(feature = "ico")]
360            ImageFormat::Ico => image::ImageFormat::Ico,
361            #[cfg(feature = "jpeg")]
362            ImageFormat::Jpeg => image::ImageFormat::Jpeg,
363            #[cfg(feature = "png")]
364            ImageFormat::Png => image::ImageFormat::Png,
365            #[cfg(feature = "pnm")]
366            ImageFormat::Pnm => image::ImageFormat::Pnm,
367            #[cfg(feature = "qoi")]
368            ImageFormat::Qoi => image::ImageFormat::Qoi,
369            #[cfg(feature = "tga")]
370            ImageFormat::Tga => image::ImageFormat::Tga,
371            #[cfg(feature = "tiff")]
372            ImageFormat::Tiff => image::ImageFormat::Tiff,
373            #[cfg(feature = "webp")]
374            ImageFormat::WebP => image::ImageFormat::WebP,
375            #[cfg(feature = "basis-universal")]
376            ImageFormat::Basis => return None,
377            #[cfg(feature = "ktx2")]
378            ImageFormat::Ktx2 => return None,
379            // FIXME: https://github.com/rust-lang/rust/issues/129031
380            #[expect(
381                clippy::allow_attributes,
382                reason = "`unreachable_patterns` may not always lint"
383            )]
384            #[allow(
385                unreachable_patterns,
386                reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
387            )]
388            _ => return None,
389        })
390    }
391
392    pub fn from_image_crate_format(format: image::ImageFormat) -> Option<ImageFormat> {
393        #[expect(
394            clippy::allow_attributes,
395            reason = "`unreachable_code` may not always lint"
396        )]
397        #[allow(
398            unreachable_code,
399            reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
400        )]
401        Some(match format {
402            image::ImageFormat::Bmp => feature_gate!("bmp", Bmp),
403            image::ImageFormat::Dds => feature_gate!("dds", Dds),
404            image::ImageFormat::Farbfeld => feature_gate!("ff", Farbfeld),
405            image::ImageFormat::Gif => feature_gate!("gif", Gif),
406            image::ImageFormat::OpenExr => feature_gate!("exr", OpenExr),
407            image::ImageFormat::Hdr => feature_gate!("hdr", Hdr),
408            image::ImageFormat::Ico => feature_gate!("ico", Ico),
409            image::ImageFormat::Jpeg => feature_gate!("jpeg", Jpeg),
410            image::ImageFormat::Png => feature_gate!("png", Png),
411            image::ImageFormat::Pnm => feature_gate!("pnm", Pnm),
412            image::ImageFormat::Qoi => feature_gate!("qoi", Qoi),
413            image::ImageFormat::Tga => feature_gate!("tga", Tga),
414            image::ImageFormat::Tiff => feature_gate!("tiff", Tiff),
415            image::ImageFormat::WebP => feature_gate!("webp", WebP),
416            _ => return None,
417        })
418    }
419}
420
421pub trait ToExtents {
422    fn to_extents(self) -> Extent3d;
423}
424impl ToExtents for UVec2 {
425    fn to_extents(self) -> Extent3d {
426        Extent3d {
427            width: self.x,
428            height: self.y,
429            depth_or_array_layers: 1,
430        }
431    }
432}
433impl ToExtents for UVec3 {
434    fn to_extents(self) -> Extent3d {
435        Extent3d {
436            width: self.x,
437            height: self.y,
438            depth_or_array_layers: self.z,
439        }
440    }
441}
442
443/// An image, optimized for usage in rendering.
444///
445/// ## Remote Inspection
446///
447/// To transmit an [`Image`] between two running Bevy apps, e.g. through BRP, use [`SerializedImage`](crate::SerializedImage).
448/// This type is only meant for short-term transmission between same versions and should not be stored anywhere.
449#[derive(Asset, Debug, Clone, PartialEq)]
450#[cfg_attr(
451    feature = "bevy_reflect",
452    derive(Reflect),
453    reflect(opaque, Default, Debug, Clone)
454)]
455#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))]
456pub struct Image {
457    /// Raw pixel data.
458    /// If the image is being used as a storage texture which doesn't need to be initialized by the
459    /// CPU, then this should be `None`.
460    /// Otherwise, it should always be `Some`.
461    pub data: Option<Vec<u8>>,
462    /// For texture data with layers and mips, this field controls how wgpu interprets the buffer layout.
463    ///
464    /// Use [`TextureDataOrder::default()`] for all other cases.
465    pub data_order: TextureDataOrder,
466    // TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors.
467    /// Describes the data layout of the GPU texture.\
468    /// For example, whether a texture contains 1D/2D/3D data, and what the format of the texture data is.
469    ///
470    /// ## Field Usage Notes
471    /// - [`TextureDescriptor::label`] is used for caching purposes when not using `Asset<Image>`.\
472    ///   If you use assets, the label is purely a debugging aid.
473    /// - [`TextureDescriptor::view_formats`] is currently unused by Bevy.
474    pub texture_descriptor: TextureDescriptor<Option<&'static str>, &'static [TextureFormat]>,
475    /// The [`ImageSampler`] to use during rendering.
476    pub sampler: ImageSampler,
477    /// Describes how the GPU texture should be interpreted.\
478    /// For example, 2D image data could be read as plain 2D, an array texture of layers of 2D with the same dimensions (and the number of layers in that case),
479    /// a cube map, an array of cube maps, etc.
480    ///
481    /// ## Field Usage Notes
482    /// - [`TextureViewDescriptor::label`] is used for caching purposes when not using `Asset<Image>`.\
483    ///   If you use assets, the label is purely a debugging aid.
484    pub texture_view_descriptor: Option<TextureViewDescriptor<Option<&'static str>>>,
485    pub asset_usage: RenderAssetUsages,
486    /// Whether this image should be copied on the GPU when resized.
487    pub copy_on_resize: bool,
488}
489
490/// Used in [`Image`], this determines what image sampler to use when rendering. The default setting,
491/// [`ImageSampler::Default`], will read the sampler from the `ImagePlugin` at setup.
492/// Setting this to [`ImageSampler::Descriptor`] will override the global default descriptor for this [`Image`].
493#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
494pub enum ImageSampler {
495    /// Default image sampler, derived from the `ImagePlugin` setup.
496    #[default]
497    Default,
498    /// Custom sampler for this image which will override global default.
499    Descriptor(ImageSamplerDescriptor),
500}
501
502impl ImageSampler {
503    /// Returns an image sampler with [`ImageFilterMode::Linear`] min and mag filters
504    #[inline]
505    pub fn linear() -> ImageSampler {
506        ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
507    }
508
509    /// Returns an image sampler with [`ImageFilterMode::Nearest`] min and mag filters
510    #[inline]
511    pub fn nearest() -> ImageSampler {
512        ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
513    }
514
515    /// Initialize the descriptor if it is not already initialized.
516    ///
517    /// Descriptor is typically initialized by Bevy when the image is loaded,
518    /// so this is convenient shortcut for updating the descriptor.
519    pub fn get_or_init_descriptor(&mut self) -> &mut ImageSamplerDescriptor {
520        match self {
521            ImageSampler::Default => {
522                *self = ImageSampler::Descriptor(ImageSamplerDescriptor::default());
523                match self {
524                    ImageSampler::Descriptor(descriptor) => descriptor,
525                    _ => unreachable!(),
526                }
527            }
528            ImageSampler::Descriptor(descriptor) => descriptor,
529        }
530    }
531}
532
533/// How edges should be handled in texture addressing.
534///
535/// See [`ImageSamplerDescriptor`] for information how to configure this.
536///
537/// This type mirrors [`AddressMode`].
538#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
539pub enum ImageAddressMode {
540    /// Clamp the value to the edge of the texture.
541    ///
542    /// -0.25 -> 0.0
543    /// 1.25  -> 1.0
544    #[default]
545    ClampToEdge,
546    /// Repeat the texture in a tiling fashion.
547    ///
548    /// -0.25 -> 0.75
549    /// 1.25 -> 0.25
550    Repeat,
551    /// Repeat the texture, mirroring it every repeat.
552    ///
553    /// -0.25 -> 0.25
554    /// 1.25 -> 0.75
555    MirrorRepeat,
556    /// Clamp the value to the border of the texture
557    /// Requires the wgpu feature [`Features::ADDRESS_MODE_CLAMP_TO_BORDER`].
558    ///
559    /// -0.25 -> border
560    /// 1.25 -> border
561    ClampToBorder,
562}
563
564/// Texel mixing mode when sampling between texels.
565///
566/// This type mirrors [`FilterMode`].
567#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
568pub enum ImageFilterMode {
569    /// Nearest neighbor sampling.
570    ///
571    /// This creates a pixelated effect when used as a mag filter.
572    #[default]
573    Nearest,
574    /// Linear Interpolation.
575    ///
576    /// This makes textures smooth but blurry when used as a mag filter.
577    Linear,
578}
579
580/// Comparison function used for depth and stencil operations.
581///
582/// This type mirrors [`CompareFunction`].
583#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
584pub enum ImageCompareFunction {
585    /// Function never passes
586    Never,
587    /// Function passes if new value less than existing value
588    Less,
589    /// Function passes if new value is equal to existing value. When using
590    /// this compare function, make sure to mark your Vertex Shader's `@builtin(position)`
591    /// output as `@invariant` to prevent artifacting.
592    Equal,
593    /// Function passes if new value is less than or equal to existing value
594    LessEqual,
595    /// Function passes if new value is greater than existing value
596    Greater,
597    /// Function passes if new value is not equal to existing value. When using
598    /// this compare function, make sure to mark your Vertex Shader's `@builtin(position)`
599    /// output as `@invariant` to prevent artifacting.
600    NotEqual,
601    /// Function passes if new value is greater than or equal to existing value
602    GreaterEqual,
603    /// Function always passes
604    Always,
605}
606
607/// Color variation to use when the sampler addressing mode is [`ImageAddressMode::ClampToBorder`].
608///
609/// This type mirrors [`SamplerBorderColor`].
610#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
611pub enum ImageSamplerBorderColor {
612    /// RGBA color `[0, 0, 0, 0]`.
613    TransparentBlack,
614    /// RGBA color `[0, 0, 0, 1]`.
615    OpaqueBlack,
616    /// RGBA color `[1, 1, 1, 1]`.
617    OpaqueWhite,
618    /// On the Metal wgpu backend, this is equivalent to [`Self::TransparentBlack`] for
619    /// textures that have an alpha component, and equivalent to [`Self::OpaqueBlack`]
620    /// for textures that do not have an alpha component. On other backends,
621    /// this is equivalent to [`Self::TransparentBlack`]. Requires
622    /// [`Features::ADDRESS_MODE_CLAMP_TO_ZERO`]. Not supported on the web.
623    Zero,
624}
625
626/// Indicates to an `ImageLoader` how an [`Image`] should be sampled.
627///
628/// As this type is part of the `ImageLoaderSettings`,
629/// it will be serialized to an image asset `.meta` file which might require a migration in case of
630/// a breaking change.
631///
632/// This types mirrors [`SamplerDescriptor`], but that might change in future versions.
633#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
634pub struct ImageSamplerDescriptor {
635    pub label: Option<String>,
636    /// How to deal with out of bounds accesses in the u (i.e. x) direction.
637    pub address_mode_u: ImageAddressMode,
638    /// How to deal with out of bounds accesses in the v (i.e. y) direction.
639    pub address_mode_v: ImageAddressMode,
640    /// How to deal with out of bounds accesses in the w (i.e. z) direction.
641    pub address_mode_w: ImageAddressMode,
642    /// How to filter the texture when it needs to be magnified (made larger).
643    pub mag_filter: ImageFilterMode,
644    /// How to filter the texture when it needs to be minified (made smaller).
645    pub min_filter: ImageFilterMode,
646    /// How to filter between mip map levels
647    pub mipmap_filter: ImageFilterMode,
648    /// Minimum level of detail (i.e. mip level) to use.
649    pub lod_min_clamp: f32,
650    /// Maximum level of detail (i.e. mip level) to use.
651    pub lod_max_clamp: f32,
652    /// If this is enabled, this is a comparison sampler using the given comparison function.
653    pub compare: Option<ImageCompareFunction>,
654    /// Must be at least 1. If this is not 1, all filter modes must be linear.
655    pub anisotropy_clamp: u16,
656    /// Border color to use when `address_mode` is [`ImageAddressMode::ClampToBorder`].
657    pub border_color: Option<ImageSamplerBorderColor>,
658}
659
660impl Default for ImageSamplerDescriptor {
661    fn default() -> Self {
662        Self {
663            address_mode_u: Default::default(),
664            address_mode_v: Default::default(),
665            address_mode_w: Default::default(),
666            mag_filter: Default::default(),
667            min_filter: Default::default(),
668            mipmap_filter: Default::default(),
669            lod_min_clamp: 0.0,
670            lod_max_clamp: 32.0,
671            compare: None,
672            anisotropy_clamp: 1,
673            border_color: None,
674            label: None,
675        }
676    }
677}
678
679impl ImageSamplerDescriptor {
680    /// Returns a sampler descriptor with [`Linear`](ImageFilterMode::Linear) min and mag filters
681    #[inline]
682    pub fn linear() -> ImageSamplerDescriptor {
683        ImageSamplerDescriptor {
684            mag_filter: ImageFilterMode::Linear,
685            min_filter: ImageFilterMode::Linear,
686            mipmap_filter: ImageFilterMode::Linear,
687            ..Default::default()
688        }
689    }
690
691    /// Returns a sampler descriptor with [`Nearest`](ImageFilterMode::Nearest) min and mag filters
692    #[inline]
693    pub fn nearest() -> ImageSamplerDescriptor {
694        ImageSamplerDescriptor {
695            mag_filter: ImageFilterMode::Nearest,
696            min_filter: ImageFilterMode::Nearest,
697            mipmap_filter: ImageFilterMode::Nearest,
698            ..Default::default()
699        }
700    }
701
702    pub fn as_wgpu(&self) -> SamplerDescriptor<Option<&str>> {
703        SamplerDescriptor {
704            label: self.label.as_deref(),
705            address_mode_u: self.address_mode_u.into(),
706            address_mode_v: self.address_mode_v.into(),
707            address_mode_w: self.address_mode_w.into(),
708            mag_filter: self.mag_filter.into(),
709            min_filter: self.min_filter.into(),
710            mipmap_filter: self.mipmap_filter.into(),
711            lod_min_clamp: self.lod_min_clamp,
712            lod_max_clamp: self.lod_max_clamp,
713            compare: self.compare.map(Into::into),
714            anisotropy_clamp: self.anisotropy_clamp,
715            border_color: self.border_color.map(Into::into),
716        }
717    }
718}
719
720impl From<ImageAddressMode> for AddressMode {
721    fn from(value: ImageAddressMode) -> Self {
722        match value {
723            ImageAddressMode::ClampToEdge => AddressMode::ClampToEdge,
724            ImageAddressMode::Repeat => AddressMode::Repeat,
725            ImageAddressMode::MirrorRepeat => AddressMode::MirrorRepeat,
726            ImageAddressMode::ClampToBorder => AddressMode::ClampToBorder,
727        }
728    }
729}
730
731impl From<ImageFilterMode> for FilterMode {
732    fn from(value: ImageFilterMode) -> Self {
733        match value {
734            ImageFilterMode::Nearest => FilterMode::Nearest,
735            ImageFilterMode::Linear => FilterMode::Linear,
736        }
737    }
738}
739
740impl From<ImageCompareFunction> for CompareFunction {
741    fn from(value: ImageCompareFunction) -> Self {
742        match value {
743            ImageCompareFunction::Never => CompareFunction::Never,
744            ImageCompareFunction::Less => CompareFunction::Less,
745            ImageCompareFunction::Equal => CompareFunction::Equal,
746            ImageCompareFunction::LessEqual => CompareFunction::LessEqual,
747            ImageCompareFunction::Greater => CompareFunction::Greater,
748            ImageCompareFunction::NotEqual => CompareFunction::NotEqual,
749            ImageCompareFunction::GreaterEqual => CompareFunction::GreaterEqual,
750            ImageCompareFunction::Always => CompareFunction::Always,
751        }
752    }
753}
754
755impl From<ImageSamplerBorderColor> for SamplerBorderColor {
756    fn from(value: ImageSamplerBorderColor) -> Self {
757        match value {
758            ImageSamplerBorderColor::TransparentBlack => SamplerBorderColor::TransparentBlack,
759            ImageSamplerBorderColor::OpaqueBlack => SamplerBorderColor::OpaqueBlack,
760            ImageSamplerBorderColor::OpaqueWhite => SamplerBorderColor::OpaqueWhite,
761            ImageSamplerBorderColor::Zero => SamplerBorderColor::Zero,
762        }
763    }
764}
765
766impl From<AddressMode> for ImageAddressMode {
767    fn from(value: AddressMode) -> Self {
768        match value {
769            AddressMode::ClampToEdge => ImageAddressMode::ClampToEdge,
770            AddressMode::Repeat => ImageAddressMode::Repeat,
771            AddressMode::MirrorRepeat => ImageAddressMode::MirrorRepeat,
772            AddressMode::ClampToBorder => ImageAddressMode::ClampToBorder,
773        }
774    }
775}
776
777impl From<FilterMode> for ImageFilterMode {
778    fn from(value: FilterMode) -> Self {
779        match value {
780            FilterMode::Nearest => ImageFilterMode::Nearest,
781            FilterMode::Linear => ImageFilterMode::Linear,
782        }
783    }
784}
785
786impl From<CompareFunction> for ImageCompareFunction {
787    fn from(value: CompareFunction) -> Self {
788        match value {
789            CompareFunction::Never => ImageCompareFunction::Never,
790            CompareFunction::Less => ImageCompareFunction::Less,
791            CompareFunction::Equal => ImageCompareFunction::Equal,
792            CompareFunction::LessEqual => ImageCompareFunction::LessEqual,
793            CompareFunction::Greater => ImageCompareFunction::Greater,
794            CompareFunction::NotEqual => ImageCompareFunction::NotEqual,
795            CompareFunction::GreaterEqual => ImageCompareFunction::GreaterEqual,
796            CompareFunction::Always => ImageCompareFunction::Always,
797        }
798    }
799}
800
801impl From<SamplerBorderColor> for ImageSamplerBorderColor {
802    fn from(value: SamplerBorderColor) -> Self {
803        match value {
804            SamplerBorderColor::TransparentBlack => ImageSamplerBorderColor::TransparentBlack,
805            SamplerBorderColor::OpaqueBlack => ImageSamplerBorderColor::OpaqueBlack,
806            SamplerBorderColor::OpaqueWhite => ImageSamplerBorderColor::OpaqueWhite,
807            SamplerBorderColor::Zero => ImageSamplerBorderColor::Zero,
808        }
809    }
810}
811
812impl From<SamplerDescriptor<Option<&str>>> for ImageSamplerDescriptor {
813    fn from(value: SamplerDescriptor<Option<&str>>) -> Self {
814        ImageSamplerDescriptor {
815            label: value.label.map(ToString::to_string),
816            address_mode_u: value.address_mode_u.into(),
817            address_mode_v: value.address_mode_v.into(),
818            address_mode_w: value.address_mode_w.into(),
819            mag_filter: value.mag_filter.into(),
820            min_filter: value.min_filter.into(),
821            mipmap_filter: value.mipmap_filter.into(),
822            lod_min_clamp: value.lod_min_clamp,
823            lod_max_clamp: value.lod_max_clamp,
824            compare: value.compare.map(Into::into),
825            anisotropy_clamp: value.anisotropy_clamp,
826            border_color: value.border_color.map(Into::into),
827        }
828    }
829}
830
831impl Default for Image {
832    /// default is a 1x1x1 all '1.0' texture
833    fn default() -> Self {
834        let mut image = Image::default_uninit();
835        image.data = Some(vec![
836            255;
837            image
838                .texture_descriptor
839                .format
840                .pixel_size()
841                .unwrap_or(0)
842        ]);
843        image
844    }
845}
846
847impl Image {
848    /// Creates a new image from raw binary data and the corresponding metadata.
849    ///
850    /// # Panics
851    /// Panics if the length of the `data`, volume of the `size` and the size of the `format`
852    /// do not match.
853    pub fn new(
854        size: Extent3d,
855        dimension: TextureDimension,
856        data: Vec<u8>,
857        format: TextureFormat,
858        asset_usage: RenderAssetUsages,
859    ) -> Self {
860        if let Ok(pixel_size) = format.pixel_size() {
861            debug_assert_eq!(
862                size.volume() * pixel_size,
863                data.len(),
864                "Pixel data, size and format have to match",
865            );
866        }
867        let mut image = Image::new_uninit(size, dimension, format, asset_usage);
868        image.data = Some(data);
869        image
870    }
871
872    /// Exactly the same as [`Image::new`], but doesn't initialize the image
873    pub fn new_uninit(
874        size: Extent3d,
875        dimension: TextureDimension,
876        format: TextureFormat,
877        asset_usage: RenderAssetUsages,
878    ) -> Self {
879        Image {
880            data: None,
881            data_order: TextureDataOrder::default(),
882            texture_descriptor: TextureDescriptor {
883                size,
884                format,
885                dimension,
886                label: None,
887                mip_level_count: 1,
888                sample_count: 1,
889                usage: TextureUsages::TEXTURE_BINDING
890                    | TextureUsages::COPY_DST
891                    | TextureUsages::COPY_SRC,
892                view_formats: &[],
893            },
894            sampler: ImageSampler::Default,
895            texture_view_descriptor: None,
896            asset_usage,
897            copy_on_resize: false,
898        }
899    }
900
901    /// A transparent white 1x1x1 image.
902    ///
903    /// Contrast to [`Image::default`], which is opaque.
904    pub fn transparent() -> Image {
905        // We rely on the default texture format being RGBA8UnormSrgb
906        // when constructing a transparent color from bytes.
907        // If this changes, this function will need to be updated.
908        let format = TextureFormat::bevy_default();
909        if let Ok(pixel_size) = format.pixel_size() {
910            debug_assert!(pixel_size == 4);
911        }
912        let data = vec![255, 255, 255, 0];
913        Image::new(
914            Extent3d::default(),
915            TextureDimension::D2,
916            data,
917            format,
918            RenderAssetUsages::default(),
919        )
920    }
921    /// Creates a new uninitialized 1x1x1 image
922    pub fn default_uninit() -> Image {
923        Image::new_uninit(
924            Extent3d::default(),
925            TextureDimension::D2,
926            TextureFormat::bevy_default(),
927            RenderAssetUsages::default(),
928        )
929    }
930
931    /// Creates a new image from raw binary data and the corresponding metadata, by filling
932    /// the image data with the `pixel` data repeated multiple times.
933    ///
934    /// # Panics
935    /// Panics if the size of the `format` is not a multiple of the length of the `pixel` data.
936    pub fn new_fill(
937        size: Extent3d,
938        dimension: TextureDimension,
939        pixel: &[u8],
940        format: TextureFormat,
941        asset_usage: RenderAssetUsages,
942    ) -> Self {
943        let mut image = Image::new_uninit(size, dimension, format, asset_usage);
944        if let Ok(pixel_size) = image.texture_descriptor.format.pixel_size()
945            && pixel_size > 0
946        {
947            let byte_len = pixel_size * size.volume();
948            debug_assert_eq!(
949                pixel.len() % pixel_size,
950                0,
951                "Must not have incomplete pixel data (pixel size is {}B).",
952                pixel_size,
953            );
954            debug_assert!(
955                pixel.len() <= byte_len,
956                "Fill data must fit within pixel buffer (expected {byte_len}B).",
957            );
958            let data = pixel.iter().copied().cycle().take(byte_len).collect();
959            image.data = Some(data);
960        }
961        image
962    }
963
964    /// Create a new zero-filled image with a given size, which can be rendered to.
965    /// Useful for mirrors, UI, or exporting images for example.
966    /// This is primarily for use as a render target for a [`Camera`].
967    /// See [`RenderTarget::Image`].
968    ///
969    /// For Standard Dynamic Range (SDR) images you can use [`TextureFormat::Rgba8UnormSrgb`].
970    /// For High Dynamic Range (HDR) images you can use [`TextureFormat::Rgba16Float`].
971    ///
972    /// The default [`TextureUsages`] are
973    /// [`TEXTURE_BINDING`](TextureUsages::TEXTURE_BINDING),
974    /// [`COPY_DST`](TextureUsages::COPY_DST),
975    /// [`RENDER_ATTACHMENT`](TextureUsages::RENDER_ATTACHMENT).
976    ///
977    /// The default [`RenderAssetUsages`] is [`MAIN_WORLD | RENDER_WORLD`](RenderAssetUsages::default)
978    /// so that it is accessible from the CPU and GPU.
979    /// You can customize this by changing the [`asset_usage`](Image::asset_usage) field.
980    ///
981    /// [`Camera`]: https://docs.rs/bevy/latest/bevy/render/camera/struct.Camera.html
982    /// [`RenderTarget::Image`]: https://docs.rs/bevy/latest/bevy/render/camera/enum.RenderTarget.html#variant.Image
983    pub fn new_target_texture(width: u32, height: u32, format: TextureFormat) -> Self {
984        let size = Extent3d {
985            width,
986            height,
987            ..Default::default()
988        };
989        // You need to set these texture usage flags in order to use the image as a render target
990        let usage = TextureUsages::TEXTURE_BINDING
991            | TextureUsages::COPY_DST
992            | TextureUsages::RENDER_ATTACHMENT;
993        // Fill with zeroes
994        let data = vec![
995            0;
996            format.pixel_size().expect(
997                "Failed to create Image: can't get pixel size for this TextureFormat"
998            ) * size.volume()
999        ];
1000
1001        Image {
1002            data: Some(data),
1003            data_order: TextureDataOrder::default(),
1004            texture_descriptor: TextureDescriptor {
1005                size,
1006                format,
1007                dimension: TextureDimension::D2,
1008                label: None,
1009                mip_level_count: 1,
1010                sample_count: 1,
1011                usage,
1012                view_formats: &[],
1013            },
1014            sampler: ImageSampler::Default,
1015            texture_view_descriptor: None,
1016            asset_usage: RenderAssetUsages::default(),
1017            copy_on_resize: true,
1018        }
1019    }
1020
1021    /// Returns the width of a 2D image.
1022    #[inline]
1023    pub fn width(&self) -> u32 {
1024        self.texture_descriptor.size.width
1025    }
1026
1027    /// Returns the height of a 2D image.
1028    #[inline]
1029    pub fn height(&self) -> u32 {
1030        self.texture_descriptor.size.height
1031    }
1032
1033    /// Returns the aspect ratio (width / height) of a 2D image.
1034    #[inline]
1035    pub fn aspect_ratio(&self) -> AspectRatio {
1036        AspectRatio::try_from_pixels(self.width(), self.height()).expect(
1037            "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
1038        )
1039    }
1040
1041    /// Returns the size of a 2D image as f32.
1042    #[inline]
1043    pub fn size_f32(&self) -> Vec2 {
1044        Vec2::new(self.width() as f32, self.height() as f32)
1045    }
1046
1047    /// Returns the size of a 2D image.
1048    #[inline]
1049    pub fn size(&self) -> UVec2 {
1050        UVec2::new(self.width(), self.height())
1051    }
1052
1053    /// Resizes the image to the new size, by removing information or appending 0 to the `data`.
1054    /// Does not properly scale the contents of the image.
1055    ///
1056    /// If you need to keep pixel data intact, use [`Image::resize_in_place`].
1057    pub fn resize(&mut self, size: Extent3d) {
1058        self.texture_descriptor.size = size;
1059        if let Some(ref mut data) = self.data
1060            && let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1061        {
1062            data.resize(size.volume() * pixel_size, 0);
1063        }
1064    }
1065
1066    /// Changes the `size`, asserting that the total number of data elements (pixels) remains the
1067    /// same.
1068    ///
1069    /// # Panics
1070    /// Panics if the `new_size` does not have the same volume as to old one.
1071    pub fn reinterpret_size(&mut self, new_size: Extent3d) {
1072        assert_eq!(
1073            new_size.volume(),
1074            self.texture_descriptor.size.volume(),
1075            "Incompatible sizes: old = {:?} new = {:?}",
1076            self.texture_descriptor.size,
1077            new_size
1078        );
1079
1080        self.texture_descriptor.size = new_size;
1081    }
1082
1083    /// Resizes the image to the new size, keeping the pixel data intact, anchored at the top-left.
1084    /// When growing, the new space is filled with 0. When shrinking, the image is clipped.
1085    ///
1086    /// For faster resizing when keeping pixel data intact is not important, use [`Image::resize`].
1087    pub fn resize_in_place(&mut self, new_size: Extent3d) {
1088        if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size() {
1089            let old_size = self.texture_descriptor.size;
1090            let byte_len = pixel_size * new_size.volume();
1091            self.texture_descriptor.size = new_size;
1092
1093            let Some(ref mut data) = self.data else {
1094                self.copy_on_resize = true;
1095                return;
1096            };
1097
1098            let mut new: Vec<u8> = vec![0; byte_len];
1099
1100            let copy_width = old_size.width.min(new_size.width) as usize;
1101            let copy_height = old_size.height.min(new_size.height) as usize;
1102            let copy_depth = old_size
1103                .depth_or_array_layers
1104                .min(new_size.depth_or_array_layers) as usize;
1105
1106            let old_row_stride = old_size.width as usize * pixel_size;
1107            let old_layer_stride = old_size.height as usize * old_row_stride;
1108
1109            let new_row_stride = new_size.width as usize * pixel_size;
1110            let new_layer_stride = new_size.height as usize * new_row_stride;
1111
1112            for z in 0..copy_depth {
1113                for y in 0..copy_height {
1114                    let old_offset = z * old_layer_stride + y * old_row_stride;
1115                    let new_offset = z * new_layer_stride + y * new_row_stride;
1116
1117                    let old_range = (old_offset)..(old_offset + copy_width * pixel_size);
1118                    let new_range = (new_offset)..(new_offset + copy_width * pixel_size);
1119
1120                    new[new_range].copy_from_slice(&data[old_range]);
1121                }
1122            }
1123
1124            self.data = Some(new);
1125        }
1126    }
1127
1128    /// Takes a 2D image containing vertically stacked images of the same size, and reinterprets
1129    /// it as a 2D array texture, where each of the stacked images becomes one layer of the
1130    /// array. This is primarily for use with the `texture2DArray` shader uniform type.
1131    ///
1132    /// # Panics
1133    /// Panics if the texture is not 2D, has more than one layers or is not evenly dividable into
1134    /// the `layers`.
1135    pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) {
1136        // Must be a stacked image, and the height must be divisible by layers.
1137        assert_eq!(self.texture_descriptor.dimension, TextureDimension::D2);
1138        assert_eq!(self.texture_descriptor.size.depth_or_array_layers, 1);
1139        assert_eq!(self.height() % layers, 0);
1140
1141        self.reinterpret_size(Extent3d {
1142            width: self.width(),
1143            height: self.height() / layers,
1144            depth_or_array_layers: layers,
1145        });
1146    }
1147
1148    /// Convert a texture from a format to another. Only a few formats are
1149    /// supported as input and output:
1150    /// - `TextureFormat::R8Unorm`
1151    /// - `TextureFormat::Rg8Unorm`
1152    /// - `TextureFormat::Rgba8UnormSrgb`
1153    ///
1154    /// To get [`Image`] as a [`image::DynamicImage`] see:
1155    /// [`Image::try_into_dynamic`].
1156    pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
1157        self.clone()
1158            .try_into_dynamic()
1159            .ok()
1160            .and_then(|img| match new_format {
1161                TextureFormat::R8Unorm => {
1162                    Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
1163                }
1164                TextureFormat::Rg8Unorm => Some((
1165                    image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
1166                    false,
1167                )),
1168                TextureFormat::Rgba8UnormSrgb => {
1169                    Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
1170                }
1171                _ => None,
1172            })
1173            .map(|(dyn_img, is_srgb)| Self::from_dynamic(dyn_img, is_srgb, self.asset_usage))
1174    }
1175
1176    /// Load a bytes buffer in a [`Image`], according to type `image_type`, using the `image`
1177    /// crate
1178    pub fn from_buffer(
1179        buffer: &[u8],
1180        image_type: ImageType,
1181        #[cfg_attr(
1182            not(any(feature = "basis-universal", feature = "dds", feature = "ktx2")),
1183            expect(unused_variables, reason = "only used with certain features")
1184        )]
1185        supported_compressed_formats: CompressedImageFormats,
1186        is_srgb: bool,
1187        image_sampler: ImageSampler,
1188        asset_usage: RenderAssetUsages,
1189    ) -> Result<Image, TextureError> {
1190        let format = image_type.to_image_format()?;
1191
1192        // Load the image in the expected format.
1193        // Some formats like PNG allow for R or RG textures too, so the texture
1194        // format needs to be determined. For RGB textures an alpha channel
1195        // needs to be added, so the image data needs to be converted in those
1196        // cases.
1197
1198        let mut image = match format {
1199            #[cfg(feature = "basis-universal")]
1200            ImageFormat::Basis => {
1201                basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
1202            }
1203            #[cfg(feature = "dds")]
1204            ImageFormat::Dds => dds_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?,
1205            #[cfg(feature = "ktx2")]
1206            ImageFormat::Ktx2 => {
1207                ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
1208            }
1209            #[expect(
1210                clippy::allow_attributes,
1211                reason = "`unreachable_patterns` may not always lint"
1212            )]
1213            #[allow(
1214                unreachable_patterns,
1215                reason = "The wildcard pattern may be unreachable if only the specially-handled formats are enabled; however, the wildcard pattern is needed for any formats not specially handled"
1216            )]
1217            _ => {
1218                let image_crate_format = format
1219                    .as_image_crate_format()
1220                    .ok_or_else(|| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?;
1221                let mut reader = image::ImageReader::new(std::io::Cursor::new(buffer));
1222                reader.set_format(image_crate_format);
1223                reader.no_limits();
1224                let dyn_img = reader.decode()?;
1225                Self::from_dynamic(dyn_img, is_srgb, asset_usage)
1226            }
1227        };
1228        image.sampler = image_sampler;
1229        Ok(image)
1230    }
1231
1232    /// Whether the texture format is compressed or uncompressed
1233    pub fn is_compressed(&self) -> bool {
1234        let format_description = self.texture_descriptor.format;
1235        format_description
1236            .required_features()
1237            .contains(Features::TEXTURE_COMPRESSION_ASTC)
1238            || format_description
1239                .required_features()
1240                .contains(Features::TEXTURE_COMPRESSION_BC)
1241            || format_description
1242                .required_features()
1243                .contains(Features::TEXTURE_COMPRESSION_ETC2)
1244    }
1245
1246    /// Compute the byte offset where the data of a specific pixel is stored
1247    ///
1248    /// Returns None if the provided coordinates are out of bounds.
1249    ///
1250    /// For 2D textures, Z is the layer number. For 1D textures, Y and Z are ignored.
1251    #[inline(always)]
1252    pub fn pixel_data_offset(&self, coords: UVec3) -> Option<usize> {
1253        let width = self.texture_descriptor.size.width;
1254        let height = self.texture_descriptor.size.height;
1255        let depth = self.texture_descriptor.size.depth_or_array_layers;
1256
1257        let pixel_size = self.texture_descriptor.format.pixel_size().ok()?;
1258        let pixel_offset = match self.texture_descriptor.dimension {
1259            TextureDimension::D3 | TextureDimension::D2 => {
1260                if coords.x >= width || coords.y >= height || coords.z >= depth {
1261                    return None;
1262                }
1263                coords.z * height * width + coords.y * width + coords.x
1264            }
1265            TextureDimension::D1 => {
1266                if coords.x >= width {
1267                    return None;
1268                }
1269                coords.x
1270            }
1271        };
1272
1273        Some(pixel_offset as usize * pixel_size)
1274    }
1275
1276    /// Get a reference to the data bytes where a specific pixel's value is stored
1277    #[inline(always)]
1278    pub fn pixel_bytes(&self, coords: UVec3) -> Option<&[u8]> {
1279        let len = self.texture_descriptor.format.pixel_size().ok()?;
1280        let data = self.data.as_ref()?;
1281        self.pixel_data_offset(coords)
1282            .map(|start| &data[start..(start + len)])
1283    }
1284
1285    /// Get a mutable reference to the data bytes where a specific pixel's value is stored
1286    #[inline(always)]
1287    pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Option<&mut [u8]> {
1288        let len = self.texture_descriptor.format.pixel_size().ok()?;
1289        let offset = self.pixel_data_offset(coords);
1290        let data = self.data.as_mut()?;
1291        offset.map(|start| &mut data[start..(start + len)])
1292    }
1293
1294    /// Clears the content of the image with the given pixel. The image needs to be initialized on
1295    /// the cpu otherwise this is a noop.
1296    ///
1297    /// This does nothing if the image data is not already initialized
1298    pub fn clear(&mut self, pixel: &[u8]) {
1299        if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1300            && pixel_size > 0
1301        {
1302            let byte_len = pixel_size * self.texture_descriptor.size.volume();
1303            debug_assert_eq!(
1304                pixel.len() % pixel_size,
1305                0,
1306                "Must not have incomplete pixel data (pixel size is {}B).",
1307                pixel_size,
1308            );
1309            debug_assert!(
1310                pixel.len() <= byte_len,
1311                "Clear data must fit within pixel buffer (expected {byte_len}B).",
1312            );
1313            if let Some(data) = self.data.as_mut() {
1314                for pixel_data in data.chunks_mut(pixel_size) {
1315                    pixel_data.copy_from_slice(pixel);
1316                }
1317            }
1318        }
1319    }
1320
1321    /// Read the color of a specific pixel (1D texture).
1322    ///
1323    /// See [`get_color_at`](Self::get_color_at) for more details.
1324    #[inline(always)]
1325    pub fn get_color_at_1d(&self, x: u32) -> Result<Color, TextureAccessError> {
1326        if self.texture_descriptor.dimension != TextureDimension::D1 {
1327            return Err(TextureAccessError::WrongDimension);
1328        }
1329        self.get_color_at_internal(UVec3::new(x, 0, 0))
1330    }
1331
1332    /// Read the color of a specific pixel (2D texture).
1333    ///
1334    /// This function will find the raw byte data of a specific pixel and
1335    /// decode it into a user-friendly [`Color`] struct for you.
1336    ///
1337    /// Supports many of the common [`TextureFormat`]s:
1338    ///  - RGBA/BGRA 8-bit unsigned integer, both sRGB and Linear
1339    ///  - 16-bit and 32-bit unsigned integer
1340    ///  - 16-bit and 32-bit float
1341    ///
1342    /// Be careful: as the data is converted to [`Color`] (which uses `f32` internally),
1343    /// there may be issues with precision when using non-f32 [`TextureFormat`]s.
1344    /// If you read a value you previously wrote using `set_color_at`, it will not match.
1345    /// If you are working with a 32-bit integer [`TextureFormat`], the value will be
1346    /// inaccurate (as `f32` does not have enough bits to represent it exactly).
1347    ///
1348    /// Single channel (R) formats are assumed to represent grayscale, so the value
1349    /// will be copied to all three RGB channels in the resulting [`Color`].
1350    ///
1351    /// Other [`TextureFormat`]s are unsupported, such as:
1352    ///  - block-compressed formats
1353    ///  - non-byte-aligned formats like 10-bit
1354    ///  - signed integer formats
1355    #[inline(always)]
1356    pub fn get_color_at(&self, x: u32, y: u32) -> Result<Color, TextureAccessError> {
1357        if self.texture_descriptor.dimension != TextureDimension::D2 {
1358            return Err(TextureAccessError::WrongDimension);
1359        }
1360        self.get_color_at_internal(UVec3::new(x, y, 0))
1361    }
1362
1363    /// Read the color of a specific pixel (2D texture with layers or 3D texture).
1364    ///
1365    /// See [`get_color_at`](Self::get_color_at) for more details.
1366    #[inline(always)]
1367    pub fn get_color_at_3d(&self, x: u32, y: u32, z: u32) -> Result<Color, TextureAccessError> {
1368        match (
1369            self.texture_descriptor.dimension,
1370            self.texture_descriptor.size.depth_or_array_layers,
1371        ) {
1372            (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1373                self.get_color_at_internal(UVec3::new(x, y, z))
1374            }
1375            _ => Err(TextureAccessError::WrongDimension),
1376        }
1377    }
1378
1379    /// Change the color of a specific pixel (1D texture).
1380    ///
1381    /// See [`set_color_at`](Self::set_color_at) for more details.
1382    #[inline(always)]
1383    pub fn set_color_at_1d(&mut self, x: u32, color: Color) -> Result<(), TextureAccessError> {
1384        if self.texture_descriptor.dimension != TextureDimension::D1 {
1385            return Err(TextureAccessError::WrongDimension);
1386        }
1387        self.set_color_at_internal(UVec3::new(x, 0, 0), color)
1388    }
1389
1390    /// Change the color of a specific pixel (2D texture).
1391    ///
1392    /// This function will find the raw byte data of a specific pixel and
1393    /// change it according to a [`Color`] you provide. The [`Color`] struct
1394    /// will be encoded into the [`Image`]'s [`TextureFormat`].
1395    ///
1396    /// Supports many of the common [`TextureFormat`]s:
1397    ///  - RGBA/BGRA 8-bit unsigned integer, both sRGB and Linear
1398    ///  - 16-bit and 32-bit unsigned integer (with possibly-limited precision, as [`Color`] uses `f32`)
1399    ///  - 16-bit and 32-bit float
1400    ///
1401    /// Be careful: writing to non-f32 [`TextureFormat`]s is lossy! The data has to be converted,
1402    /// so if you read it back using `get_color_at`, the `Color` you get will not equal the value
1403    /// you used when writing it using this function.
1404    ///
1405    /// For R and RG formats, only the respective values from the linear RGB [`Color`] will be used.
1406    ///
1407    /// Other [`TextureFormat`]s are unsupported, such as:
1408    ///  - block-compressed formats
1409    ///  - non-byte-aligned formats like 10-bit
1410    ///  - signed integer formats
1411    #[inline(always)]
1412    pub fn set_color_at(&mut self, x: u32, y: u32, color: Color) -> Result<(), TextureAccessError> {
1413        if self.texture_descriptor.dimension != TextureDimension::D2 {
1414            return Err(TextureAccessError::WrongDimension);
1415        }
1416        self.set_color_at_internal(UVec3::new(x, y, 0), color)
1417    }
1418
1419    /// Change the color of a specific pixel (2D texture with layers or 3D texture).
1420    ///
1421    /// See [`set_color_at`](Self::set_color_at) for more details.
1422    #[inline(always)]
1423    pub fn set_color_at_3d(
1424        &mut self,
1425        x: u32,
1426        y: u32,
1427        z: u32,
1428        color: Color,
1429    ) -> Result<(), TextureAccessError> {
1430        match (
1431            self.texture_descriptor.dimension,
1432            self.texture_descriptor.size.depth_or_array_layers,
1433        ) {
1434            (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1435                self.set_color_at_internal(UVec3::new(x, y, z), color)
1436            }
1437            _ => Err(TextureAccessError::WrongDimension),
1438        }
1439    }
1440
1441    #[inline(always)]
1442    fn get_color_at_internal(&self, coords: UVec3) -> Result<Color, TextureAccessError> {
1443        let Some(bytes) = self.pixel_bytes(coords) else {
1444            return Err(TextureAccessError::OutOfBounds {
1445                x: coords.x,
1446                y: coords.y,
1447                z: coords.z,
1448            });
1449        };
1450
1451        // NOTE: GPUs are always Little Endian.
1452        // Make sure to respect that when we create color values from bytes.
1453        match self.texture_descriptor.format {
1454            TextureFormat::Rgba8UnormSrgb => Ok(Color::srgba(
1455                bytes[0] as f32 / u8::MAX as f32,
1456                bytes[1] as f32 / u8::MAX as f32,
1457                bytes[2] as f32 / u8::MAX as f32,
1458                bytes[3] as f32 / u8::MAX as f32,
1459            )),
1460            TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => Ok(Color::linear_rgba(
1461                bytes[0] as f32 / u8::MAX as f32,
1462                bytes[1] as f32 / u8::MAX as f32,
1463                bytes[2] as f32 / u8::MAX as f32,
1464                bytes[3] as f32 / u8::MAX as f32,
1465            )),
1466            TextureFormat::Bgra8UnormSrgb => Ok(Color::srgba(
1467                bytes[2] as f32 / u8::MAX as f32,
1468                bytes[1] as f32 / u8::MAX as f32,
1469                bytes[0] as f32 / u8::MAX as f32,
1470                bytes[3] as f32 / u8::MAX as f32,
1471            )),
1472            TextureFormat::Bgra8Unorm => Ok(Color::linear_rgba(
1473                bytes[2] as f32 / u8::MAX as f32,
1474                bytes[1] as f32 / u8::MAX as f32,
1475                bytes[0] as f32 / u8::MAX as f32,
1476                bytes[3] as f32 / u8::MAX as f32,
1477            )),
1478            TextureFormat::Rgba32Float => Ok(Color::linear_rgba(
1479                f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1480                f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1481                f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1482                f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1483            )),
1484            TextureFormat::Rgba16Float => Ok(Color::linear_rgba(
1485                half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32(),
1486                half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32(),
1487                half::f16::from_le_bytes([bytes[4], bytes[5]]).to_f32(),
1488                half::f16::from_le_bytes([bytes[6], bytes[7]]).to_f32(),
1489            )),
1490            TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1491                let (r, g, b, a) = (
1492                    u16::from_le_bytes([bytes[0], bytes[1]]),
1493                    u16::from_le_bytes([bytes[2], bytes[3]]),
1494                    u16::from_le_bytes([bytes[4], bytes[5]]),
1495                    u16::from_le_bytes([bytes[6], bytes[7]]),
1496                );
1497                Ok(Color::linear_rgba(
1498                    // going via f64 to avoid rounding errors with large numbers and division
1499                    (r as f64 / u16::MAX as f64) as f32,
1500                    (g as f64 / u16::MAX as f64) as f32,
1501                    (b as f64 / u16::MAX as f64) as f32,
1502                    (a as f64 / u16::MAX as f64) as f32,
1503                ))
1504            }
1505            TextureFormat::Rgba32Uint => {
1506                let (r, g, b, a) = (
1507                    u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1508                    u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1509                    u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1510                    u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1511                );
1512                Ok(Color::linear_rgba(
1513                    // going via f64 to avoid rounding errors with large numbers and division
1514                    (r as f64 / u32::MAX as f64) as f32,
1515                    (g as f64 / u32::MAX as f64) as f32,
1516                    (b as f64 / u32::MAX as f64) as f32,
1517                    (a as f64 / u32::MAX as f64) as f32,
1518                ))
1519            }
1520            // assume R-only texture format means grayscale (linear)
1521            // copy value to all of RGB in Color
1522            TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1523                let x = bytes[0] as f32 / u8::MAX as f32;
1524                Ok(Color::linear_rgb(x, x, x))
1525            }
1526            TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1527                let x = u16::from_le_bytes([bytes[0], bytes[1]]);
1528                // going via f64 to avoid rounding errors with large numbers and division
1529                let x = (x as f64 / u16::MAX as f64) as f32;
1530                Ok(Color::linear_rgb(x, x, x))
1531            }
1532            TextureFormat::R32Uint => {
1533                let x = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1534                // going via f64 to avoid rounding errors with large numbers and division
1535                let x = (x as f64 / u32::MAX as f64) as f32;
1536                Ok(Color::linear_rgb(x, x, x))
1537            }
1538            TextureFormat::R16Float => {
1539                let x = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1540                Ok(Color::linear_rgb(x, x, x))
1541            }
1542            TextureFormat::R32Float => {
1543                let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1544                Ok(Color::linear_rgb(x, x, x))
1545            }
1546            TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1547                let r = bytes[0] as f32 / u8::MAX as f32;
1548                let g = bytes[1] as f32 / u8::MAX as f32;
1549                Ok(Color::linear_rgb(r, g, 0.0))
1550            }
1551            TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1552                let r = u16::from_le_bytes([bytes[0], bytes[1]]);
1553                let g = u16::from_le_bytes([bytes[2], bytes[3]]);
1554                // going via f64 to avoid rounding errors with large numbers and division
1555                let r = (r as f64 / u16::MAX as f64) as f32;
1556                let g = (g as f64 / u16::MAX as f64) as f32;
1557                Ok(Color::linear_rgb(r, g, 0.0))
1558            }
1559            TextureFormat::Rg32Uint => {
1560                let r = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1561                let g = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1562                // going via f64 to avoid rounding errors with large numbers and division
1563                let r = (r as f64 / u32::MAX as f64) as f32;
1564                let g = (g as f64 / u32::MAX as f64) as f32;
1565                Ok(Color::linear_rgb(r, g, 0.0))
1566            }
1567            TextureFormat::Rg16Float => {
1568                let r = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1569                let g = half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32();
1570                Ok(Color::linear_rgb(r, g, 0.0))
1571            }
1572            TextureFormat::Rg32Float => {
1573                let r = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1574                let g = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1575                Ok(Color::linear_rgb(r, g, 0.0))
1576            }
1577            _ => Err(TextureAccessError::UnsupportedTextureFormat(
1578                self.texture_descriptor.format,
1579            )),
1580        }
1581    }
1582
1583    #[inline(always)]
1584    fn set_color_at_internal(
1585        &mut self,
1586        coords: UVec3,
1587        color: Color,
1588    ) -> Result<(), TextureAccessError> {
1589        let format = self.texture_descriptor.format;
1590
1591        let Some(bytes) = self.pixel_bytes_mut(coords) else {
1592            return Err(TextureAccessError::OutOfBounds {
1593                x: coords.x,
1594                y: coords.y,
1595                z: coords.z,
1596            });
1597        };
1598
1599        // NOTE: GPUs are always Little Endian.
1600        // Make sure to respect that when we convert color values to bytes.
1601        match format {
1602            TextureFormat::Rgba8UnormSrgb => {
1603                let [r, g, b, a] = Srgba::from(color).to_f32_array();
1604                bytes[0] = (r * u8::MAX as f32) as u8;
1605                bytes[1] = (g * u8::MAX as f32) as u8;
1606                bytes[2] = (b * u8::MAX as f32) as u8;
1607                bytes[3] = (a * u8::MAX as f32) as u8;
1608            }
1609            TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => {
1610                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1611                bytes[0] = (r * u8::MAX as f32) as u8;
1612                bytes[1] = (g * u8::MAX as f32) as u8;
1613                bytes[2] = (b * u8::MAX as f32) as u8;
1614                bytes[3] = (a * u8::MAX as f32) as u8;
1615            }
1616            TextureFormat::Bgra8UnormSrgb => {
1617                let [r, g, b, a] = Srgba::from(color).to_f32_array();
1618                bytes[0] = (b * u8::MAX as f32) as u8;
1619                bytes[1] = (g * u8::MAX as f32) as u8;
1620                bytes[2] = (r * u8::MAX as f32) as u8;
1621                bytes[3] = (a * u8::MAX as f32) as u8;
1622            }
1623            TextureFormat::Bgra8Unorm => {
1624                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1625                bytes[0] = (b * u8::MAX as f32) as u8;
1626                bytes[1] = (g * u8::MAX as f32) as u8;
1627                bytes[2] = (r * u8::MAX as f32) as u8;
1628                bytes[3] = (a * u8::MAX as f32) as u8;
1629            }
1630            TextureFormat::Rgba16Float => {
1631                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1632                bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1633                bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1634                bytes[4..6].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(b)));
1635                bytes[6..8].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(a)));
1636            }
1637            TextureFormat::Rgba32Float => {
1638                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1639                bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1640                bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1641                bytes[8..12].copy_from_slice(&f32::to_le_bytes(b));
1642                bytes[12..16].copy_from_slice(&f32::to_le_bytes(a));
1643            }
1644            TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1645                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1646                let [r, g, b, a] = [
1647                    (r * u16::MAX as f32) as u16,
1648                    (g * u16::MAX as f32) as u16,
1649                    (b * u16::MAX as f32) as u16,
1650                    (a * u16::MAX as f32) as u16,
1651                ];
1652                bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1653                bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1654                bytes[4..6].copy_from_slice(&u16::to_le_bytes(b));
1655                bytes[6..8].copy_from_slice(&u16::to_le_bytes(a));
1656            }
1657            TextureFormat::Rgba32Uint => {
1658                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1659                let [r, g, b, a] = [
1660                    (r * u32::MAX as f32) as u32,
1661                    (g * u32::MAX as f32) as u32,
1662                    (b * u32::MAX as f32) as u32,
1663                    (a * u32::MAX as f32) as u32,
1664                ];
1665                bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1666                bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1667                bytes[8..12].copy_from_slice(&u32::to_le_bytes(b));
1668                bytes[12..16].copy_from_slice(&u32::to_le_bytes(a));
1669            }
1670            TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1671                // Convert to grayscale with minimal loss if color is already gray
1672                let linear = LinearRgba::from(color);
1673                let luminance = Xyza::from(linear).y;
1674                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1675                bytes[0] = (r * u8::MAX as f32) as u8;
1676            }
1677            TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1678                // Convert to grayscale with minimal loss if color is already gray
1679                let linear = LinearRgba::from(color);
1680                let luminance = Xyza::from(linear).y;
1681                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1682                let r = (r * u16::MAX as f32) as u16;
1683                bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1684            }
1685            TextureFormat::R32Uint => {
1686                // Convert to grayscale with minimal loss if color is already gray
1687                let linear = LinearRgba::from(color);
1688                let luminance = Xyza::from(linear).y;
1689                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1690                // go via f64 to avoid imprecision
1691                let r = (r as f64 * u32::MAX as f64) as u32;
1692                bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1693            }
1694            TextureFormat::R16Float => {
1695                // Convert to grayscale with minimal loss if color is already gray
1696                let linear = LinearRgba::from(color);
1697                let luminance = Xyza::from(linear).y;
1698                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1699                let x = half::f16::from_f32(r);
1700                bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(x));
1701            }
1702            TextureFormat::R32Float => {
1703                // Convert to grayscale with minimal loss if color is already gray
1704                let linear = LinearRgba::from(color);
1705                let luminance = Xyza::from(linear).y;
1706                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1707                bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1708            }
1709            TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1710                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1711                bytes[0] = (r * u8::MAX as f32) as u8;
1712                bytes[1] = (g * u8::MAX as f32) as u8;
1713            }
1714            TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1715                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1716                let r = (r * u16::MAX as f32) as u16;
1717                let g = (g * u16::MAX as f32) as u16;
1718                bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1719                bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1720            }
1721            TextureFormat::Rg32Uint => {
1722                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1723                // go via f64 to avoid imprecision
1724                let r = (r as f64 * u32::MAX as f64) as u32;
1725                let g = (g as f64 * u32::MAX as f64) as u32;
1726                bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1727                bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1728            }
1729            TextureFormat::Rg16Float => {
1730                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1731                bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1732                bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1733            }
1734            TextureFormat::Rg32Float => {
1735                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1736                bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1737                bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1738            }
1739            _ => {
1740                return Err(TextureAccessError::UnsupportedTextureFormat(
1741                    self.texture_descriptor.format,
1742                ));
1743            }
1744        }
1745        Ok(())
1746    }
1747}
1748
1749#[derive(Clone, Copy, Debug)]
1750pub enum DataFormat {
1751    Rgb,
1752    Rgba,
1753    Rrr,
1754    Rrrg,
1755    Rg,
1756}
1757
1758/// Texture data need to be transcoded from this format for use with `wgpu`.
1759#[derive(Clone, Copy, Debug)]
1760pub enum TranscodeFormat {
1761    Etc1s,
1762    Uastc(DataFormat),
1763    /// Has to be transcoded from `R8UnormSrgb` to `R8Unorm` for use with `wgpu`.
1764    R8UnormSrgb,
1765    /// Has to be transcoded from `Rg8UnormSrgb` to `R8G8Unorm` for use with `wgpu`.
1766    Rg8UnormSrgb,
1767    /// Has to be transcoded from `Rgb8` to `Rgba8` for use with `wgpu`.
1768    Rgb8,
1769}
1770
1771/// An error that occurs when accessing specific pixels in a texture.
1772#[derive(Error, Debug)]
1773pub enum TextureAccessError {
1774    #[error("out of bounds (x: {x}, y: {y}, z: {z})")]
1775    OutOfBounds { x: u32, y: u32, z: u32 },
1776    #[error("unsupported texture format: {0:?}")]
1777    UnsupportedTextureFormat(TextureFormat),
1778    #[error("attempt to access texture with different dimension")]
1779    WrongDimension,
1780}
1781
1782/// An error that occurs when loading a texture.
1783#[derive(Error, Debug)]
1784pub enum TextureError {
1785    /// Image MIME type is invalid.
1786    #[error("invalid image mime type: {0}")]
1787    InvalidImageMimeType(String),
1788    /// Image extension is invalid.
1789    #[error("invalid image extension: {0}")]
1790    InvalidImageExtension(String),
1791    /// Failed to load an image.
1792    #[error("failed to load an image: {0}")]
1793    ImageError(#[from] image::ImageError),
1794    /// Texture format isn't supported.
1795    #[error("unsupported texture format: {0}")]
1796    UnsupportedTextureFormat(String),
1797    /// Supercompression isn't supported.
1798    #[error("supercompression not supported: {0}")]
1799    SuperCompressionNotSupported(String),
1800    /// Failed to decompress an image.
1801    #[error("failed to decompress an image: {0}")]
1802    SuperDecompressionError(String),
1803    /// Invalid data.
1804    #[error("invalid data: {0}")]
1805    InvalidData(String),
1806    /// Transcode error.
1807    #[error("transcode error: {0}")]
1808    TranscodeError(String),
1809    /// Format requires transcoding.
1810    #[error("format requires transcoding: {0:?}")]
1811    FormatRequiresTranscodingError(TranscodeFormat),
1812    /// Only cubemaps with six faces are supported.
1813    #[error("only cubemaps with six faces are supported")]
1814    IncompleteCubemap,
1815}
1816
1817/// The type of a raw image buffer.
1818#[derive(Debug)]
1819pub enum ImageType<'a> {
1820    /// The mime type of an image, for example `"image/png"`.
1821    MimeType(&'a str),
1822    /// The extension of an image file, for example `"png"`.
1823    Extension(&'a str),
1824    /// The direct format of the image
1825    Format(ImageFormat),
1826}
1827
1828impl<'a> ImageType<'a> {
1829    pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
1830        match self {
1831            ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
1832                .ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
1833            ImageType::Extension(extension) => ImageFormat::from_extension(extension)
1834                .ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
1835            ImageType::Format(format) => Ok(*format),
1836        }
1837    }
1838}
1839
1840/// Used to calculate the volume of an item.
1841pub trait Volume {
1842    fn volume(&self) -> usize;
1843}
1844
1845impl Volume for Extent3d {
1846    /// Calculates the volume of the [`Extent3d`].
1847    fn volume(&self) -> usize {
1848        (self.width * self.height * self.depth_or_array_layers) as usize
1849    }
1850}
1851
1852/// Extends the wgpu [`TextureFormat`] with information about the pixel.
1853pub trait TextureFormatPixelInfo {
1854    /// Returns the size of a pixel in bytes of the format.
1855    /// error with `TextureAccessError::UnsupportedTextureFormat` if the format is compressed.
1856    fn pixel_size(&self) -> Result<usize, TextureAccessError>;
1857}
1858
1859impl TextureFormatPixelInfo for TextureFormat {
1860    fn pixel_size(&self) -> Result<usize, TextureAccessError> {
1861        let info = self;
1862        match info.block_dimensions() {
1863            (1, 1) => Ok(info.block_copy_size(None).unwrap() as usize),
1864            _ => Err(TextureAccessError::UnsupportedTextureFormat(*self)),
1865        }
1866    }
1867}
1868
1869bitflags::bitflags! {
1870    #[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
1871    #[repr(transparent)]
1872    pub struct CompressedImageFormats: u32 {
1873        const NONE     = 0;
1874        const ASTC_LDR = 1 << 0;
1875        const BC       = 1 << 1;
1876        const ETC2     = 1 << 2;
1877    }
1878}
1879
1880impl CompressedImageFormats {
1881    pub fn from_features(features: Features) -> Self {
1882        let mut supported_compressed_formats = Self::default();
1883        if features.contains(Features::TEXTURE_COMPRESSION_ASTC) {
1884            supported_compressed_formats |= Self::ASTC_LDR;
1885        }
1886        if features.contains(Features::TEXTURE_COMPRESSION_BC) {
1887            supported_compressed_formats |= Self::BC;
1888        }
1889        if features.contains(Features::TEXTURE_COMPRESSION_ETC2) {
1890            supported_compressed_formats |= Self::ETC2;
1891        }
1892        supported_compressed_formats
1893    }
1894
1895    pub fn supports(&self, format: TextureFormat) -> bool {
1896        match format {
1897            TextureFormat::Bc1RgbaUnorm
1898            | TextureFormat::Bc1RgbaUnormSrgb
1899            | TextureFormat::Bc2RgbaUnorm
1900            | TextureFormat::Bc2RgbaUnormSrgb
1901            | TextureFormat::Bc3RgbaUnorm
1902            | TextureFormat::Bc3RgbaUnormSrgb
1903            | TextureFormat::Bc4RUnorm
1904            | TextureFormat::Bc4RSnorm
1905            | TextureFormat::Bc5RgUnorm
1906            | TextureFormat::Bc5RgSnorm
1907            | TextureFormat::Bc6hRgbUfloat
1908            | TextureFormat::Bc6hRgbFloat
1909            | TextureFormat::Bc7RgbaUnorm
1910            | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
1911            TextureFormat::Etc2Rgb8Unorm
1912            | TextureFormat::Etc2Rgb8UnormSrgb
1913            | TextureFormat::Etc2Rgb8A1Unorm
1914            | TextureFormat::Etc2Rgb8A1UnormSrgb
1915            | TextureFormat::Etc2Rgba8Unorm
1916            | TextureFormat::Etc2Rgba8UnormSrgb
1917            | TextureFormat::EacR11Unorm
1918            | TextureFormat::EacR11Snorm
1919            | TextureFormat::EacRg11Unorm
1920            | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
1921            TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
1922            _ => true,
1923        }
1924    }
1925}
1926
1927/// For defining which compressed image formats are supported. This will be initialized from available device features
1928/// in `finish()` of the bevy `RenderPlugin`, but is left for the user to specify if not using the `RenderPlugin`, or
1929/// the WGPU backend.
1930#[derive(Resource)]
1931pub struct CompressedImageFormatSupport(pub CompressedImageFormats);
1932
1933#[cfg(test)]
1934mod test {
1935    use super::*;
1936
1937    #[test]
1938    fn image_size() {
1939        let size = Extent3d {
1940            width: 200,
1941            height: 100,
1942            depth_or_array_layers: 1,
1943        };
1944        let image = Image::new_fill(
1945            size,
1946            TextureDimension::D2,
1947            &[0, 0, 0, 255],
1948            TextureFormat::Rgba8Unorm,
1949            RenderAssetUsages::MAIN_WORLD,
1950        );
1951        assert_eq!(
1952            Vec2::new(size.width as f32, size.height as f32),
1953            image.size_f32()
1954        );
1955    }
1956
1957    #[test]
1958    fn image_default_size() {
1959        let image = Image::default();
1960        assert_eq!(UVec2::ONE, image.size());
1961        assert_eq!(Vec2::ONE, image.size_f32());
1962    }
1963
1964    #[test]
1965    fn on_edge_pixel_is_invalid() {
1966        let image = Image::new_fill(
1967            Extent3d {
1968                width: 5,
1969                height: 10,
1970                depth_or_array_layers: 1,
1971            },
1972            TextureDimension::D2,
1973            &[0, 0, 0, 255],
1974            TextureFormat::Rgba8Unorm,
1975            RenderAssetUsages::MAIN_WORLD,
1976        );
1977        assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK)));
1978        assert!(matches!(
1979            image.get_color_at(0, 10),
1980            Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 })
1981        ));
1982        assert!(matches!(
1983            image.get_color_at(5, 10),
1984            Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 })
1985        ));
1986    }
1987
1988    #[test]
1989    fn get_set_pixel_2d_with_layers() {
1990        let mut image = Image::new_fill(
1991            Extent3d {
1992                width: 5,
1993                height: 10,
1994                depth_or_array_layers: 3,
1995            },
1996            TextureDimension::D2,
1997            &[0, 0, 0, 255],
1998            TextureFormat::Rgba8Unorm,
1999            RenderAssetUsages::MAIN_WORLD,
2000        );
2001        image.set_color_at_3d(0, 0, 0, Color::WHITE).unwrap();
2002        assert!(matches!(image.get_color_at_3d(0, 0, 0), Ok(Color::WHITE)));
2003        image.set_color_at_3d(2, 3, 1, Color::WHITE).unwrap();
2004        assert!(matches!(image.get_color_at_3d(2, 3, 1), Ok(Color::WHITE)));
2005        image.set_color_at_3d(4, 9, 2, Color::WHITE).unwrap();
2006        assert!(matches!(image.get_color_at_3d(4, 9, 2), Ok(Color::WHITE)));
2007    }
2008
2009    #[test]
2010    fn resize_in_place_2d_grow_and_shrink() {
2011        use bevy_color::ColorToPacked;
2012
2013        const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2014        const GROW_FILL: LinearRgba = LinearRgba::NONE;
2015
2016        let mut image = Image::new_fill(
2017            Extent3d {
2018                width: 2,
2019                height: 2,
2020                depth_or_array_layers: 1,
2021            },
2022            TextureDimension::D2,
2023            &INITIAL_FILL.to_u8_array(),
2024            TextureFormat::Rgba8Unorm,
2025            RenderAssetUsages::MAIN_WORLD,
2026        );
2027
2028        // Create a test pattern
2029
2030        const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2031            (0, 1, LinearRgba::RED),
2032            (1, 1, LinearRgba::GREEN),
2033            (1, 0, LinearRgba::BLUE),
2034        ];
2035
2036        for (x, y, color) in &TEST_PIXELS {
2037            image.set_color_at(*x, *y, Color::from(*color)).unwrap();
2038        }
2039
2040        // Grow image
2041        image.resize_in_place(Extent3d {
2042            width: 4,
2043            height: 4,
2044            depth_or_array_layers: 1,
2045        });
2046
2047        // After growing, the test pattern should be the same.
2048        assert!(matches!(
2049            image.get_color_at(0, 0),
2050            Ok(Color::LinearRgba(INITIAL_FILL))
2051        ));
2052        for (x, y, color) in &TEST_PIXELS {
2053            assert_eq!(
2054                image.get_color_at(*x, *y).unwrap(),
2055                Color::LinearRgba(*color)
2056            );
2057        }
2058
2059        // Pixels in the newly added area should get filled with zeroes.
2060        assert!(matches!(
2061            image.get_color_at(3, 3),
2062            Ok(Color::LinearRgba(GROW_FILL))
2063        ));
2064
2065        // Shrink
2066        image.resize_in_place(Extent3d {
2067            width: 1,
2068            height: 1,
2069            depth_or_array_layers: 1,
2070        });
2071
2072        // Images outside of the new dimensions should be clipped
2073        assert!(image.get_color_at(1, 1).is_err());
2074    }
2075
2076    #[test]
2077    fn resize_in_place_array_grow_and_shrink() {
2078        use bevy_color::ColorToPacked;
2079
2080        const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2081        const GROW_FILL: LinearRgba = LinearRgba::NONE;
2082        const LAYERS: u32 = 4;
2083
2084        let mut image = Image::new_fill(
2085            Extent3d {
2086                width: 2,
2087                height: 2,
2088                depth_or_array_layers: LAYERS,
2089            },
2090            TextureDimension::D2,
2091            &INITIAL_FILL.to_u8_array(),
2092            TextureFormat::Rgba8Unorm,
2093            RenderAssetUsages::MAIN_WORLD,
2094        );
2095
2096        // Create a test pattern
2097
2098        const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2099            (0, 1, LinearRgba::RED),
2100            (1, 1, LinearRgba::GREEN),
2101            (1, 0, LinearRgba::BLUE),
2102        ];
2103
2104        for z in 0..LAYERS {
2105            for (x, y, color) in &TEST_PIXELS {
2106                image
2107                    .set_color_at_3d(*x, *y, z, Color::from(*color))
2108                    .unwrap();
2109            }
2110        }
2111
2112        // Grow image
2113        image.resize_in_place(Extent3d {
2114            width: 4,
2115            height: 4,
2116            depth_or_array_layers: LAYERS + 1,
2117        });
2118
2119        // After growing, the test pattern should be the same.
2120        assert!(matches!(
2121            image.get_color_at(0, 0),
2122            Ok(Color::LinearRgba(INITIAL_FILL))
2123        ));
2124        for z in 0..LAYERS {
2125            for (x, y, color) in &TEST_PIXELS {
2126                assert_eq!(
2127                    image.get_color_at_3d(*x, *y, z).unwrap(),
2128                    Color::LinearRgba(*color)
2129                );
2130            }
2131        }
2132
2133        // Pixels in the newly added area should get filled with zeroes.
2134        for z in 0..(LAYERS + 1) {
2135            assert!(matches!(
2136                image.get_color_at_3d(3, 3, z),
2137                Ok(Color::LinearRgba(GROW_FILL))
2138            ));
2139        }
2140
2141        // Shrink
2142        image.resize_in_place(Extent3d {
2143            width: 1,
2144            height: 1,
2145            depth_or_array_layers: 1,
2146        });
2147
2148        // Images outside of the new dimensions should be clipped
2149        assert!(image.get_color_at_3d(1, 1, 0).is_err());
2150
2151        // Higher layers should no longer be present
2152        assert!(image.get_color_at_3d(0, 0, 1).is_err());
2153
2154        // Grow layers
2155        image.resize_in_place(Extent3d {
2156            width: 1,
2157            height: 1,
2158            depth_or_array_layers: 2,
2159        });
2160
2161        // Pixels in the newly added layer should be zeroes.
2162        assert!(matches!(
2163            image.get_color_at_3d(0, 0, 1),
2164            Ok(Color::LinearRgba(GROW_FILL))
2165        ));
2166    }
2167
2168    #[test]
2169    fn image_clear() {
2170        let mut image = Image::new_fill(
2171            Extent3d {
2172                width: 32,
2173                height: 32,
2174                depth_or_array_layers: 1,
2175            },
2176            TextureDimension::D2,
2177            &[0; 4],
2178            TextureFormat::Rgba8Snorm,
2179            RenderAssetUsages::all(),
2180        );
2181
2182        assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 0));
2183
2184        image.clear(&[255; 4]);
2185
2186        assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 255));
2187    }
2188}