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