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        image.asset_usage = asset_usage;
1409        Ok(image)
1410    }
1411
1412    /// Whether the texture format is compressed or uncompressed
1413    pub fn is_compressed(&self) -> bool {
1414        let format_description = self.texture_descriptor.format;
1415        format_description
1416            .required_features()
1417            .contains(Features::TEXTURE_COMPRESSION_ASTC)
1418            || format_description
1419                .required_features()
1420                .contains(Features::TEXTURE_COMPRESSION_BC)
1421            || format_description
1422                .required_features()
1423                .contains(Features::TEXTURE_COMPRESSION_ETC2)
1424    }
1425
1426    /// Compute the byte offset where the data of a specific pixel is stored
1427    ///
1428    /// Returns None if the provided coordinates are out of bounds.
1429    ///
1430    /// For 2D textures, Z is the layer number. For 1D textures, Y and Z are ignored.
1431    #[inline(always)]
1432    pub fn pixel_data_offset(&self, coords: UVec3) -> Option<usize> {
1433        let width = self.texture_descriptor.size.width;
1434        let height = self.texture_descriptor.size.height;
1435        let depth = self.texture_descriptor.size.depth_or_array_layers;
1436
1437        let pixel_size = self.texture_descriptor.format.pixel_size().ok()?;
1438        let pixel_offset = match self.texture_descriptor.dimension {
1439            TextureDimension::D3 | TextureDimension::D2 => {
1440                if coords.x >= width || coords.y >= height || coords.z >= depth {
1441                    return None;
1442                }
1443                coords.z * height * width + coords.y * width + coords.x
1444            }
1445            TextureDimension::D1 => {
1446                if coords.x >= width {
1447                    return None;
1448                }
1449                coords.x
1450            }
1451        };
1452
1453        Some(pixel_offset as usize * pixel_size)
1454    }
1455
1456    /// Get a reference to the data bytes where a specific pixel's value is stored
1457    #[inline(always)]
1458    pub fn pixel_bytes(&self, coords: UVec3) -> Option<&[u8]> {
1459        let len = self.texture_descriptor.format.pixel_size().ok()?;
1460        let data = self.data.as_ref()?;
1461        self.pixel_data_offset(coords)
1462            .map(|start| &data[start..(start + len)])
1463    }
1464
1465    /// Get a mutable reference to the data bytes where a specific pixel's value is stored
1466    #[inline(always)]
1467    pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Option<&mut [u8]> {
1468        let len = self.texture_descriptor.format.pixel_size().ok()?;
1469        let offset = self.pixel_data_offset(coords);
1470        let data = self.data.as_mut()?;
1471        offset.map(|start| &mut data[start..(start + len)])
1472    }
1473
1474    /// Clears the content of the image with the given pixel. The image needs to be initialized on
1475    /// the cpu otherwise this is a noop.
1476    ///
1477    /// This does nothing if the image data is not already initialized
1478    pub fn clear(&mut self, pixel: &[u8]) {
1479        if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1480            && pixel_size > 0
1481        {
1482            let byte_len = pixel_size * self.texture_descriptor.size.volume();
1483            debug_assert_eq!(
1484                pixel.len() % pixel_size,
1485                0,
1486                "Must not have incomplete pixel data (pixel size is {}B).",
1487                pixel_size,
1488            );
1489            debug_assert!(
1490                pixel.len() <= byte_len,
1491                "Clear data must fit within pixel buffer (expected {byte_len}B).",
1492            );
1493            if let Some(data) = self.data.as_mut() {
1494                for pixel_data in data.chunks_mut(pixel_size) {
1495                    pixel_data.copy_from_slice(pixel);
1496                }
1497            }
1498        }
1499    }
1500
1501    /// Read the color of a specific pixel (1D texture).
1502    ///
1503    /// See [`get_color_at`](Self::get_color_at) for more details.
1504    #[inline(always)]
1505    pub fn get_color_at_1d(&self, x: u32) -> Result<Color, TextureAccessError> {
1506        if self.texture_descriptor.dimension != TextureDimension::D1 {
1507            return Err(TextureAccessError::WrongDimension);
1508        }
1509        self.get_color_at_internal(UVec3::new(x, 0, 0))
1510    }
1511
1512    /// Read the color of a specific pixel (2D texture).
1513    ///
1514    /// This function will find the raw byte data of a specific pixel and
1515    /// decode it into a user-friendly [`Color`] struct for you.
1516    ///
1517    /// Supports many of the common [`TextureFormat`]s:
1518    ///  - RGBA/BGRA 8-bit unsigned integer, both sRGB and Linear
1519    ///  - 16-bit and 32-bit unsigned integer
1520    ///  - 16-bit and 32-bit float
1521    ///
1522    /// Be careful: as the data is converted to [`Color`] (which uses `f32` internally),
1523    /// there may be issues with precision when using non-f32 [`TextureFormat`]s.
1524    /// If you read a value you previously wrote using `set_color_at`, it will not match.
1525    /// If you are working with a 32-bit integer [`TextureFormat`], the value will be
1526    /// inaccurate (as `f32` does not have enough bits to represent it exactly).
1527    ///
1528    /// Single channel (R) formats are assumed to represent grayscale, so the value
1529    /// will be copied to all three RGB channels in the resulting [`Color`].
1530    ///
1531    /// Other [`TextureFormat`]s are unsupported, such as:
1532    ///  - block-compressed formats
1533    ///  - non-byte-aligned formats like 10-bit
1534    ///  - signed integer formats
1535    #[inline(always)]
1536    pub fn get_color_at(&self, x: u32, y: u32) -> Result<Color, TextureAccessError> {
1537        if self.texture_descriptor.dimension != TextureDimension::D2 {
1538            return Err(TextureAccessError::WrongDimension);
1539        }
1540        self.get_color_at_internal(UVec3::new(x, y, 0))
1541    }
1542
1543    /// Read the color of a specific pixel (2D texture with layers or 3D texture).
1544    ///
1545    /// See [`get_color_at`](Self::get_color_at) for more details.
1546    #[inline(always)]
1547    pub fn get_color_at_3d(&self, x: u32, y: u32, z: u32) -> Result<Color, TextureAccessError> {
1548        match (
1549            self.texture_descriptor.dimension,
1550            self.texture_descriptor.size.depth_or_array_layers,
1551        ) {
1552            (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1553                self.get_color_at_internal(UVec3::new(x, y, z))
1554            }
1555            _ => Err(TextureAccessError::WrongDimension),
1556        }
1557    }
1558
1559    /// Change the color of a specific pixel (1D texture).
1560    ///
1561    /// See [`set_color_at`](Self::set_color_at) for more details.
1562    #[inline(always)]
1563    pub fn set_color_at_1d(&mut self, x: u32, color: Color) -> Result<(), TextureAccessError> {
1564        if self.texture_descriptor.dimension != TextureDimension::D1 {
1565            return Err(TextureAccessError::WrongDimension);
1566        }
1567        self.set_color_at_internal(UVec3::new(x, 0, 0), color)
1568    }
1569
1570    /// Change the color of a specific pixel (2D texture).
1571    ///
1572    /// This function will find the raw byte data of a specific pixel and
1573    /// change it according to a [`Color`] you provide. The [`Color`] struct
1574    /// will be encoded into the [`Image`]'s [`TextureFormat`].
1575    ///
1576    /// Supports many of the common [`TextureFormat`]s:
1577    ///  - RGBA/BGRA 8-bit unsigned integer, both sRGB and Linear
1578    ///  - 16-bit and 32-bit unsigned integer (with possibly-limited precision, as [`Color`] uses `f32`)
1579    ///  - 16-bit and 32-bit float
1580    ///
1581    /// Be careful: writing to non-f32 [`TextureFormat`]s is lossy! The data has to be converted,
1582    /// so if you read it back using `get_color_at`, the `Color` you get will not equal the value
1583    /// you used when writing it using this function.
1584    ///
1585    /// For R and RG formats, only the respective values from the linear RGB [`Color`] will be used.
1586    ///
1587    /// Other [`TextureFormat`]s are unsupported, such as:
1588    ///  - block-compressed formats
1589    ///  - non-byte-aligned formats like 10-bit
1590    ///  - signed integer formats
1591    #[inline(always)]
1592    pub fn set_color_at(&mut self, x: u32, y: u32, color: Color) -> Result<(), TextureAccessError> {
1593        if self.texture_descriptor.dimension != TextureDimension::D2 {
1594            return Err(TextureAccessError::WrongDimension);
1595        }
1596        self.set_color_at_internal(UVec3::new(x, y, 0), color)
1597    }
1598
1599    /// Change the color of a specific pixel (2D texture with layers or 3D texture).
1600    ///
1601    /// See [`set_color_at`](Self::set_color_at) for more details.
1602    #[inline(always)]
1603    pub fn set_color_at_3d(
1604        &mut self,
1605        x: u32,
1606        y: u32,
1607        z: u32,
1608        color: Color,
1609    ) -> Result<(), TextureAccessError> {
1610        match (
1611            self.texture_descriptor.dimension,
1612            self.texture_descriptor.size.depth_or_array_layers,
1613        ) {
1614            (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1615                self.set_color_at_internal(UVec3::new(x, y, z), color)
1616            }
1617            _ => Err(TextureAccessError::WrongDimension),
1618        }
1619    }
1620
1621    #[inline(always)]
1622    fn get_color_at_internal(&self, coords: UVec3) -> Result<Color, TextureAccessError> {
1623        let Some(bytes) = self.pixel_bytes(coords) else {
1624            return Err(TextureAccessError::OutOfBounds {
1625                x: coords.x,
1626                y: coords.y,
1627                z: coords.z,
1628            });
1629        };
1630
1631        // NOTE: GPUs are always Little Endian.
1632        // Make sure to respect that when we create color values from bytes.
1633        match self.texture_descriptor.format {
1634            TextureFormat::Rgba8UnormSrgb => Ok(Color::srgba(
1635                bytes[0] as f32 / u8::MAX as f32,
1636                bytes[1] as f32 / u8::MAX as f32,
1637                bytes[2] as f32 / u8::MAX as f32,
1638                bytes[3] as f32 / u8::MAX as f32,
1639            )),
1640            TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => Ok(Color::linear_rgba(
1641                bytes[0] as f32 / u8::MAX as f32,
1642                bytes[1] as f32 / u8::MAX as f32,
1643                bytes[2] as f32 / u8::MAX as f32,
1644                bytes[3] as f32 / u8::MAX as f32,
1645            )),
1646            TextureFormat::Bgra8UnormSrgb => Ok(Color::srgba(
1647                bytes[2] as f32 / u8::MAX as f32,
1648                bytes[1] as f32 / u8::MAX as f32,
1649                bytes[0] as f32 / u8::MAX as f32,
1650                bytes[3] as f32 / u8::MAX as f32,
1651            )),
1652            TextureFormat::Bgra8Unorm => Ok(Color::linear_rgba(
1653                bytes[2] as f32 / u8::MAX as f32,
1654                bytes[1] as f32 / u8::MAX as f32,
1655                bytes[0] as f32 / u8::MAX as f32,
1656                bytes[3] as f32 / u8::MAX as f32,
1657            )),
1658            TextureFormat::Rgba32Float => Ok(Color::linear_rgba(
1659                f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1660                f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1661                f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1662                f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1663            )),
1664            TextureFormat::Rgba16Float => Ok(Color::linear_rgba(
1665                half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32(),
1666                half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32(),
1667                half::f16::from_le_bytes([bytes[4], bytes[5]]).to_f32(),
1668                half::f16::from_le_bytes([bytes[6], bytes[7]]).to_f32(),
1669            )),
1670            TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1671                let (r, g, b, a) = (
1672                    u16::from_le_bytes([bytes[0], bytes[1]]),
1673                    u16::from_le_bytes([bytes[2], bytes[3]]),
1674                    u16::from_le_bytes([bytes[4], bytes[5]]),
1675                    u16::from_le_bytes([bytes[6], bytes[7]]),
1676                );
1677                Ok(Color::linear_rgba(
1678                    // going via f64 to avoid rounding errors with large numbers and division
1679                    (r as f64 / u16::MAX as f64) as f32,
1680                    (g as f64 / u16::MAX as f64) as f32,
1681                    (b as f64 / u16::MAX as f64) as f32,
1682                    (a as f64 / u16::MAX as f64) as f32,
1683                ))
1684            }
1685            TextureFormat::Rgba32Uint => {
1686                let (r, g, b, a) = (
1687                    u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1688                    u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1689                    u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1690                    u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1691                );
1692                Ok(Color::linear_rgba(
1693                    // going via f64 to avoid rounding errors with large numbers and division
1694                    (r as f64 / u32::MAX as f64) as f32,
1695                    (g as f64 / u32::MAX as f64) as f32,
1696                    (b as f64 / u32::MAX as f64) as f32,
1697                    (a as f64 / u32::MAX as f64) as f32,
1698                ))
1699            }
1700            // assume R-only texture format means grayscale (linear)
1701            // copy value to all of RGB in Color
1702            TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1703                let x = bytes[0] as f32 / u8::MAX as f32;
1704                Ok(Color::linear_rgb(x, x, x))
1705            }
1706            TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1707                let x = u16::from_le_bytes([bytes[0], bytes[1]]);
1708                // going via f64 to avoid rounding errors with large numbers and division
1709                let x = (x as f64 / u16::MAX as f64) as f32;
1710                Ok(Color::linear_rgb(x, x, x))
1711            }
1712            TextureFormat::R32Uint => {
1713                let x = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1714                // going via f64 to avoid rounding errors with large numbers and division
1715                let x = (x as f64 / u32::MAX as f64) as f32;
1716                Ok(Color::linear_rgb(x, x, x))
1717            }
1718            TextureFormat::R16Float => {
1719                let x = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1720                Ok(Color::linear_rgb(x, x, x))
1721            }
1722            TextureFormat::R32Float => {
1723                let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1724                Ok(Color::linear_rgb(x, x, x))
1725            }
1726            TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1727                let r = bytes[0] as f32 / u8::MAX as f32;
1728                let g = bytes[1] as f32 / u8::MAX as f32;
1729                Ok(Color::linear_rgb(r, g, 0.0))
1730            }
1731            TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1732                let r = u16::from_le_bytes([bytes[0], bytes[1]]);
1733                let g = u16::from_le_bytes([bytes[2], bytes[3]]);
1734                // going via f64 to avoid rounding errors with large numbers and division
1735                let r = (r as f64 / u16::MAX as f64) as f32;
1736                let g = (g as f64 / u16::MAX as f64) as f32;
1737                Ok(Color::linear_rgb(r, g, 0.0))
1738            }
1739            TextureFormat::Rg32Uint => {
1740                let r = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1741                let g = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1742                // going via f64 to avoid rounding errors with large numbers and division
1743                let r = (r as f64 / u32::MAX as f64) as f32;
1744                let g = (g as f64 / u32::MAX as f64) as f32;
1745                Ok(Color::linear_rgb(r, g, 0.0))
1746            }
1747            TextureFormat::Rg16Float => {
1748                let r = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1749                let g = half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32();
1750                Ok(Color::linear_rgb(r, g, 0.0))
1751            }
1752            TextureFormat::Rg32Float => {
1753                let r = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1754                let g = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1755                Ok(Color::linear_rgb(r, g, 0.0))
1756            }
1757            _ => Err(TextureAccessError::UnsupportedTextureFormat(
1758                self.texture_descriptor.format,
1759            )),
1760        }
1761    }
1762
1763    #[inline(always)]
1764    fn set_color_at_internal(
1765        &mut self,
1766        coords: UVec3,
1767        color: Color,
1768    ) -> Result<(), TextureAccessError> {
1769        let format = self.texture_descriptor.format;
1770
1771        let Some(bytes) = self.pixel_bytes_mut(coords) else {
1772            return Err(TextureAccessError::OutOfBounds {
1773                x: coords.x,
1774                y: coords.y,
1775                z: coords.z,
1776            });
1777        };
1778
1779        // NOTE: GPUs are always Little Endian.
1780        // Make sure to respect that when we convert color values to bytes.
1781        match format {
1782            TextureFormat::Rgba8UnormSrgb => {
1783                let [r, g, b, a] = Srgba::from(color).to_f32_array();
1784                bytes[0] = (r * u8::MAX as f32) as u8;
1785                bytes[1] = (g * u8::MAX as f32) as u8;
1786                bytes[2] = (b * u8::MAX as f32) as u8;
1787                bytes[3] = (a * u8::MAX as f32) as u8;
1788            }
1789            TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => {
1790                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1791                bytes[0] = (r * u8::MAX as f32) as u8;
1792                bytes[1] = (g * u8::MAX as f32) as u8;
1793                bytes[2] = (b * u8::MAX as f32) as u8;
1794                bytes[3] = (a * u8::MAX as f32) as u8;
1795            }
1796            TextureFormat::Bgra8UnormSrgb => {
1797                let [r, g, b, a] = Srgba::from(color).to_f32_array();
1798                bytes[0] = (b * u8::MAX as f32) as u8;
1799                bytes[1] = (g * u8::MAX as f32) as u8;
1800                bytes[2] = (r * u8::MAX as f32) as u8;
1801                bytes[3] = (a * u8::MAX as f32) as u8;
1802            }
1803            TextureFormat::Bgra8Unorm => {
1804                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1805                bytes[0] = (b * u8::MAX as f32) as u8;
1806                bytes[1] = (g * u8::MAX as f32) as u8;
1807                bytes[2] = (r * u8::MAX as f32) as u8;
1808                bytes[3] = (a * u8::MAX as f32) as u8;
1809            }
1810            TextureFormat::Rgba16Float => {
1811                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1812                bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1813                bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1814                bytes[4..6].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(b)));
1815                bytes[6..8].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(a)));
1816            }
1817            TextureFormat::Rgba32Float => {
1818                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1819                bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1820                bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1821                bytes[8..12].copy_from_slice(&f32::to_le_bytes(b));
1822                bytes[12..16].copy_from_slice(&f32::to_le_bytes(a));
1823            }
1824            TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1825                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1826                let [r, g, b, a] = [
1827                    (r * u16::MAX as f32) as u16,
1828                    (g * u16::MAX as f32) as u16,
1829                    (b * u16::MAX as f32) as u16,
1830                    (a * u16::MAX as f32) as u16,
1831                ];
1832                bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1833                bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1834                bytes[4..6].copy_from_slice(&u16::to_le_bytes(b));
1835                bytes[6..8].copy_from_slice(&u16::to_le_bytes(a));
1836            }
1837            TextureFormat::Rgba32Uint => {
1838                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1839                let [r, g, b, a] = [
1840                    (r * u32::MAX as f32) as u32,
1841                    (g * u32::MAX as f32) as u32,
1842                    (b * u32::MAX as f32) as u32,
1843                    (a * u32::MAX as f32) as u32,
1844                ];
1845                bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1846                bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1847                bytes[8..12].copy_from_slice(&u32::to_le_bytes(b));
1848                bytes[12..16].copy_from_slice(&u32::to_le_bytes(a));
1849            }
1850            TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1851                // Convert to grayscale with minimal loss if color is already gray
1852                let linear = LinearRgba::from(color);
1853                let luminance = Xyza::from(linear).y;
1854                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1855                bytes[0] = (r * u8::MAX as f32) as u8;
1856            }
1857            TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1858                // Convert to grayscale with minimal loss if color is already gray
1859                let linear = LinearRgba::from(color);
1860                let luminance = Xyza::from(linear).y;
1861                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1862                let r = (r * u16::MAX as f32) as u16;
1863                bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1864            }
1865            TextureFormat::R32Uint => {
1866                // Convert to grayscale with minimal loss if color is already gray
1867                let linear = LinearRgba::from(color);
1868                let luminance = Xyza::from(linear).y;
1869                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1870                // go via f64 to avoid imprecision
1871                let r = (r as f64 * u32::MAX as f64) as u32;
1872                bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1873            }
1874            TextureFormat::R16Float => {
1875                // Convert to grayscale with minimal loss if color is already gray
1876                let linear = LinearRgba::from(color);
1877                let luminance = Xyza::from(linear).y;
1878                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1879                let x = half::f16::from_f32(r);
1880                bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(x));
1881            }
1882            TextureFormat::R32Float => {
1883                // Convert to grayscale with minimal loss if color is already gray
1884                let linear = LinearRgba::from(color);
1885                let luminance = Xyza::from(linear).y;
1886                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1887                bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1888            }
1889            TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1890                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1891                bytes[0] = (r * u8::MAX as f32) as u8;
1892                bytes[1] = (g * u8::MAX as f32) as u8;
1893            }
1894            TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1895                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1896                let r = (r * u16::MAX as f32) as u16;
1897                let g = (g * u16::MAX as f32) as u16;
1898                bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1899                bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1900            }
1901            TextureFormat::Rg32Uint => {
1902                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1903                // go via f64 to avoid imprecision
1904                let r = (r as f64 * u32::MAX as f64) as u32;
1905                let g = (g as f64 * u32::MAX as f64) as u32;
1906                bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1907                bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1908            }
1909            TextureFormat::Rg16Float => {
1910                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1911                bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1912                bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1913            }
1914            TextureFormat::Rg32Float => {
1915                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1916                bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1917                bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1918            }
1919            _ => {
1920                return Err(TextureAccessError::UnsupportedTextureFormat(
1921                    self.texture_descriptor.format,
1922                ));
1923            }
1924        }
1925        Ok(())
1926    }
1927}
1928
1929#[derive(Clone, Copy, Debug)]
1930pub enum DataFormat {
1931    Rgb,
1932    Rgba,
1933    Rrr,
1934    Rrrg,
1935    Rg,
1936}
1937
1938/// Texture data need to be transcoded from this format for use with `wgpu`.
1939#[derive(Clone, Copy, Debug)]
1940pub enum TranscodeFormat {
1941    Etc1s,
1942    Uastc(DataFormat),
1943    /// Has to be transcoded from `R8UnormSrgb` to `R8Unorm` for use with `wgpu`.
1944    R8UnormSrgb,
1945    /// Has to be transcoded from `Rg8UnormSrgb` to `R8G8Unorm` for use with `wgpu`.
1946    Rg8UnormSrgb,
1947    /// Has to be transcoded from `Rgb8` to `Rgba8` for use with `wgpu`.
1948    Rgb8,
1949}
1950
1951/// An error that occurs when reinterpreting an image.
1952#[derive(Error, Debug)]
1953pub enum TextureReinterpretationError {
1954    #[error("incompatible sizes: old = {old:?} new = {new:?}")]
1955    IncompatibleSizes { old: Extent3d, new: Extent3d },
1956    #[error("must be a 2d image")]
1957    WrongDimension,
1958    #[error("must not already be a layered image")]
1959    InvalidLayerCount,
1960    #[error("can not evenly divide height = {height} by layers = {layers}")]
1961    HeightNotDivisibleByLayers { height: u32, layers: u32 },
1962}
1963
1964/// An error that occurs when accessing specific pixels in a texture.
1965#[derive(Error, Debug)]
1966pub enum TextureAccessError {
1967    #[error("out of bounds (x: {x}, y: {y}, z: {z})")]
1968    OutOfBounds { x: u32, y: u32, z: u32 },
1969    #[error("unsupported texture format: {0:?}")]
1970    UnsupportedTextureFormat(TextureFormat),
1971    #[error("attempt to access texture with different dimension")]
1972    WrongDimension,
1973}
1974
1975/// An error that occurs when loading a texture.
1976#[derive(Error, Debug)]
1977pub enum TextureError {
1978    /// Image MIME type is invalid.
1979    #[error("invalid image mime type: {0}")]
1980    InvalidImageMimeType(String),
1981    /// Image extension is invalid.
1982    #[error("invalid image extension: {0}")]
1983    InvalidImageExtension(String),
1984    /// Failed to load an image.
1985    #[error("failed to load an image: {0}")]
1986    ImageError(#[from] image::ImageError),
1987    /// Texture format isn't supported.
1988    #[error("unsupported texture format: {0}")]
1989    UnsupportedTextureFormat(String),
1990    /// Supercompression isn't supported.
1991    #[error("supercompression not supported: {0}")]
1992    SuperCompressionNotSupported(String),
1993    /// Failed to decompress an image.
1994    #[error("failed to decompress an image: {0}")]
1995    SuperDecompressionError(String),
1996    /// Invalid data.
1997    #[error("invalid data: {0}")]
1998    InvalidData(String),
1999    /// Transcode error.
2000    #[error("transcode error: {0}")]
2001    TranscodeError(String),
2002    /// Format requires transcoding.
2003    #[error("format requires transcoding: {0:?}")]
2004    FormatRequiresTranscodingError(TranscodeFormat),
2005    /// Only cubemaps with six faces are supported.
2006    #[error("only cubemaps with six faces are supported")]
2007    IncompleteCubemap,
2008}
2009
2010/// The type of a raw image buffer.
2011#[derive(Debug)]
2012pub enum ImageType<'a> {
2013    /// The mime type of an image, for example `"image/png"`.
2014    MimeType(&'a str),
2015    /// The extension of an image file, for example `"png"`.
2016    Extension(&'a str),
2017    /// The direct format of the image
2018    Format(ImageFormat),
2019}
2020
2021impl<'a> ImageType<'a> {
2022    pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
2023        match self {
2024            ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
2025                .ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
2026            ImageType::Extension(extension) => ImageFormat::from_extension(extension)
2027                .ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
2028            ImageType::Format(format) => Ok(*format),
2029        }
2030    }
2031}
2032
2033/// Used to calculate the volume of an item.
2034pub trait Volume {
2035    fn volume(&self) -> usize;
2036}
2037
2038impl Volume for Extent3d {
2039    /// Calculates the volume of the [`Extent3d`].
2040    fn volume(&self) -> usize {
2041        (self.width * self.height * self.depth_or_array_layers) as usize
2042    }
2043}
2044
2045/// Extends the wgpu [`TextureFormat`] with information about the pixel.
2046pub trait TextureFormatPixelInfo {
2047    /// Returns the size of a pixel in bytes of the format.
2048    /// error with `TextureAccessError::UnsupportedTextureFormat` if the format is compressed.
2049    fn pixel_size(&self) -> Result<usize, TextureAccessError>;
2050}
2051
2052impl TextureFormatPixelInfo for TextureFormat {
2053    fn pixel_size(&self) -> Result<usize, TextureAccessError> {
2054        let info = self;
2055        match info.block_dimensions() {
2056            (1, 1) => Ok(info.block_copy_size(None).unwrap() as usize),
2057            _ => Err(TextureAccessError::UnsupportedTextureFormat(*self)),
2058        }
2059    }
2060}
2061
2062bitflags::bitflags! {
2063    #[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
2064    #[repr(transparent)]
2065    pub struct CompressedImageFormats: u32 {
2066        const NONE     = 0;
2067        const ASTC_LDR = 1 << 0;
2068        const BC       = 1 << 1;
2069        const ETC2     = 1 << 2;
2070    }
2071}
2072
2073impl CompressedImageFormats {
2074    pub fn from_features(features: Features) -> Self {
2075        let mut supported_compressed_formats = Self::default();
2076        if features.contains(Features::TEXTURE_COMPRESSION_ASTC) {
2077            supported_compressed_formats |= Self::ASTC_LDR;
2078        }
2079        if features.contains(Features::TEXTURE_COMPRESSION_BC) {
2080            supported_compressed_formats |= Self::BC;
2081        }
2082        if features.contains(Features::TEXTURE_COMPRESSION_ETC2) {
2083            supported_compressed_formats |= Self::ETC2;
2084        }
2085        supported_compressed_formats
2086    }
2087
2088    pub fn supports(&self, format: TextureFormat) -> bool {
2089        match format {
2090            TextureFormat::Bc1RgbaUnorm
2091            | TextureFormat::Bc1RgbaUnormSrgb
2092            | TextureFormat::Bc2RgbaUnorm
2093            | TextureFormat::Bc2RgbaUnormSrgb
2094            | TextureFormat::Bc3RgbaUnorm
2095            | TextureFormat::Bc3RgbaUnormSrgb
2096            | TextureFormat::Bc4RUnorm
2097            | TextureFormat::Bc4RSnorm
2098            | TextureFormat::Bc5RgUnorm
2099            | TextureFormat::Bc5RgSnorm
2100            | TextureFormat::Bc6hRgbUfloat
2101            | TextureFormat::Bc6hRgbFloat
2102            | TextureFormat::Bc7RgbaUnorm
2103            | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
2104            TextureFormat::Etc2Rgb8Unorm
2105            | TextureFormat::Etc2Rgb8UnormSrgb
2106            | TextureFormat::Etc2Rgb8A1Unorm
2107            | TextureFormat::Etc2Rgb8A1UnormSrgb
2108            | TextureFormat::Etc2Rgba8Unorm
2109            | TextureFormat::Etc2Rgba8UnormSrgb
2110            | TextureFormat::EacR11Unorm
2111            | TextureFormat::EacR11Snorm
2112            | TextureFormat::EacRg11Unorm
2113            | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
2114            TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
2115            _ => true,
2116        }
2117    }
2118}
2119
2120/// For defining which compressed image formats are supported. This will be initialized from available device features
2121/// in `finish()` of the bevy `RenderPlugin`, but is left for the user to specify if not using the `RenderPlugin`, or
2122/// the WGPU backend.
2123#[derive(Resource)]
2124pub struct CompressedImageFormatSupport(pub CompressedImageFormats);
2125
2126#[cfg(test)]
2127mod test {
2128    use super::*;
2129
2130    #[test]
2131    fn image_size() {
2132        let size = Extent3d {
2133            width: 200,
2134            height: 100,
2135            depth_or_array_layers: 1,
2136        };
2137        let image = Image::new_fill(
2138            size,
2139            TextureDimension::D2,
2140            &[0, 0, 0, 255],
2141            TextureFormat::Rgba8Unorm,
2142            RenderAssetUsages::MAIN_WORLD,
2143        );
2144        assert_eq!(
2145            Vec2::new(size.width as f32, size.height as f32),
2146            image.size_f32()
2147        );
2148    }
2149
2150    #[test]
2151    fn image_default_size() {
2152        let image = Image::default();
2153        assert_eq!(UVec2::ONE, image.size());
2154        assert_eq!(Vec2::ONE, image.size_f32());
2155    }
2156
2157    #[test]
2158    fn on_edge_pixel_is_invalid() {
2159        let image = Image::new_fill(
2160            Extent3d {
2161                width: 5,
2162                height: 10,
2163                depth_or_array_layers: 1,
2164            },
2165            TextureDimension::D2,
2166            &[0, 0, 0, 255],
2167            TextureFormat::Rgba8Unorm,
2168            RenderAssetUsages::MAIN_WORLD,
2169        );
2170        assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK)));
2171        assert!(matches!(
2172            image.get_color_at(0, 10),
2173            Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 })
2174        ));
2175        assert!(matches!(
2176            image.get_color_at(5, 10),
2177            Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 })
2178        ));
2179    }
2180
2181    #[test]
2182    fn get_set_pixel_2d_with_layers() {
2183        let mut image = Image::new_fill(
2184            Extent3d {
2185                width: 5,
2186                height: 10,
2187                depth_or_array_layers: 3,
2188            },
2189            TextureDimension::D2,
2190            &[0, 0, 0, 255],
2191            TextureFormat::Rgba8Unorm,
2192            RenderAssetUsages::MAIN_WORLD,
2193        );
2194        image.set_color_at_3d(0, 0, 0, Color::WHITE).unwrap();
2195        assert!(matches!(image.get_color_at_3d(0, 0, 0), Ok(Color::WHITE)));
2196        image.set_color_at_3d(2, 3, 1, Color::WHITE).unwrap();
2197        assert!(matches!(image.get_color_at_3d(2, 3, 1), Ok(Color::WHITE)));
2198        image.set_color_at_3d(4, 9, 2, Color::WHITE).unwrap();
2199        assert!(matches!(image.get_color_at_3d(4, 9, 2), Ok(Color::WHITE)));
2200    }
2201
2202    #[test]
2203    fn resize_in_place_2d_grow_and_shrink() {
2204        use bevy_color::ColorToPacked;
2205
2206        const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2207        const GROW_FILL: LinearRgba = LinearRgba::NONE;
2208
2209        let mut image = Image::new_fill(
2210            Extent3d {
2211                width: 2,
2212                height: 2,
2213                depth_or_array_layers: 1,
2214            },
2215            TextureDimension::D2,
2216            &INITIAL_FILL.to_u8_array(),
2217            TextureFormat::Rgba8Unorm,
2218            RenderAssetUsages::MAIN_WORLD,
2219        );
2220
2221        // Create a test pattern
2222
2223        const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2224            (0, 1, LinearRgba::RED),
2225            (1, 1, LinearRgba::GREEN),
2226            (1, 0, LinearRgba::BLUE),
2227        ];
2228
2229        for (x, y, color) in &TEST_PIXELS {
2230            image.set_color_at(*x, *y, Color::from(*color)).unwrap();
2231        }
2232
2233        // Grow image
2234        image.resize_in_place(Extent3d {
2235            width: 4,
2236            height: 4,
2237            depth_or_array_layers: 1,
2238        });
2239
2240        // After growing, the test pattern should be the same.
2241        assert!(matches!(
2242            image.get_color_at(0, 0),
2243            Ok(Color::LinearRgba(INITIAL_FILL))
2244        ));
2245        for (x, y, color) in &TEST_PIXELS {
2246            assert_eq!(
2247                image.get_color_at(*x, *y).unwrap(),
2248                Color::LinearRgba(*color)
2249            );
2250        }
2251
2252        // Pixels in the newly added area should get filled with zeroes.
2253        assert!(matches!(
2254            image.get_color_at(3, 3),
2255            Ok(Color::LinearRgba(GROW_FILL))
2256        ));
2257
2258        // Shrink
2259        image.resize_in_place(Extent3d {
2260            width: 1,
2261            height: 1,
2262            depth_or_array_layers: 1,
2263        });
2264
2265        // Images outside of the new dimensions should be clipped
2266        assert!(image.get_color_at(1, 1).is_err());
2267    }
2268
2269    #[test]
2270    fn resize_in_place_array_grow_and_shrink() {
2271        use bevy_color::ColorToPacked;
2272
2273        const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2274        const GROW_FILL: LinearRgba = LinearRgba::NONE;
2275        const LAYERS: u32 = 4;
2276
2277        let mut image = Image::new_fill(
2278            Extent3d {
2279                width: 2,
2280                height: 2,
2281                depth_or_array_layers: LAYERS,
2282            },
2283            TextureDimension::D2,
2284            &INITIAL_FILL.to_u8_array(),
2285            TextureFormat::Rgba8Unorm,
2286            RenderAssetUsages::MAIN_WORLD,
2287        );
2288
2289        // Create a test pattern
2290
2291        const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2292            (0, 1, LinearRgba::RED),
2293            (1, 1, LinearRgba::GREEN),
2294            (1, 0, LinearRgba::BLUE),
2295        ];
2296
2297        for z in 0..LAYERS {
2298            for (x, y, color) in &TEST_PIXELS {
2299                image
2300                    .set_color_at_3d(*x, *y, z, Color::from(*color))
2301                    .unwrap();
2302            }
2303        }
2304
2305        // Grow image
2306        image.resize_in_place(Extent3d {
2307            width: 4,
2308            height: 4,
2309            depth_or_array_layers: LAYERS + 1,
2310        });
2311
2312        // After growing, the test pattern should be the same.
2313        assert!(matches!(
2314            image.get_color_at(0, 0),
2315            Ok(Color::LinearRgba(INITIAL_FILL))
2316        ));
2317        for z in 0..LAYERS {
2318            for (x, y, color) in &TEST_PIXELS {
2319                assert_eq!(
2320                    image.get_color_at_3d(*x, *y, z).unwrap(),
2321                    Color::LinearRgba(*color)
2322                );
2323            }
2324        }
2325
2326        // Pixels in the newly added area should get filled with zeroes.
2327        for z in 0..(LAYERS + 1) {
2328            assert!(matches!(
2329                image.get_color_at_3d(3, 3, z),
2330                Ok(Color::LinearRgba(GROW_FILL))
2331            ));
2332        }
2333
2334        // Shrink
2335        image.resize_in_place(Extent3d {
2336            width: 1,
2337            height: 1,
2338            depth_or_array_layers: 1,
2339        });
2340
2341        // Images outside of the new dimensions should be clipped
2342        assert!(image.get_color_at_3d(1, 1, 0).is_err());
2343
2344        // Higher layers should no longer be present
2345        assert!(image.get_color_at_3d(0, 0, 1).is_err());
2346
2347        // Grow layers
2348        image.resize_in_place(Extent3d {
2349            width: 1,
2350            height: 1,
2351            depth_or_array_layers: 2,
2352        });
2353
2354        // Pixels in the newly added layer should be zeroes.
2355        assert!(matches!(
2356            image.get_color_at_3d(0, 0, 1),
2357            Ok(Color::LinearRgba(GROW_FILL))
2358        ));
2359    }
2360
2361    #[test]
2362    fn image_clear() {
2363        let mut image = Image::new_fill(
2364            Extent3d {
2365                width: 32,
2366                height: 32,
2367                depth_or_array_layers: 1,
2368            },
2369            TextureDimension::D2,
2370            &[0; 4],
2371            TextureFormat::Rgba8Snorm,
2372            RenderAssetUsages::all(),
2373        );
2374
2375        assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 0));
2376
2377        image.clear(&[255; 4]);
2378
2379        assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 255));
2380    }
2381
2382    #[test]
2383    fn get_or_init_sampler_modifications() {
2384        // given some sampler
2385        let mut default_sampler = ImageSampler::Default;
2386        // a load_with_settings call wants to customize the descriptor
2387        let my_sampler_in_a_loader = default_sampler
2388            .get_or_init_descriptor()
2389            .set_filter(ImageFilterMode::Linear)
2390            .set_address_mode(ImageAddressMode::Repeat);
2391
2392        assert_eq!(
2393            my_sampler_in_a_loader.address_mode_u,
2394            ImageAddressMode::Repeat
2395        );
2396        assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2397    }
2398
2399    #[test]
2400    fn get_or_init_sampler_anisotropy() {
2401        // given some sampler
2402        let mut default_sampler = ImageSampler::Default;
2403        // a load_with_settings call wants to customize the descriptor
2404        let my_sampler_in_a_loader = default_sampler
2405            .get_or_init_descriptor()
2406            .set_anisotropic_filter(8);
2407
2408        assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2409        assert_eq!(my_sampler_in_a_loader.anisotropy_clamp, 8);
2410    }
2411}