Skip to main content

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