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