bevy_image/
image.rs

1#[cfg(feature = "basis-universal")]
2use super::basis::*;
3#[cfg(feature = "dds")]
4use super::dds::*;
5#[cfg(feature = "ktx2")]
6use super::ktx2::*;
7
8use bevy_asset::{Asset, RenderAssetUsages};
9use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza};
10use bevy_math::{AspectRatio, UVec2, UVec3, Vec2};
11use bevy_reflect::std_traits::ReflectDefault;
12use bevy_reflect::Reflect;
13use core::hash::Hash;
14use derive_more::derive::{Display, Error, From};
15use serde::{Deserialize, Serialize};
16use wgpu::{Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor};
17
18pub trait BevyDefault {
19    fn bevy_default() -> Self;
20}
21
22impl BevyDefault for TextureFormat {
23    fn bevy_default() -> Self {
24        TextureFormat::Rgba8UnormSrgb
25    }
26}
27
28pub const TEXTURE_ASSET_INDEX: u64 = 0;
29pub const SAMPLER_ASSET_INDEX: u64 = 1;
30
31#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
32pub enum ImageFormat {
33    #[cfg(feature = "basis-universal")]
34    Basis,
35    #[cfg(feature = "bmp")]
36    Bmp,
37    #[cfg(feature = "dds")]
38    Dds,
39    #[cfg(feature = "ff")]
40    Farbfeld,
41    #[cfg(feature = "gif")]
42    Gif,
43    #[cfg(feature = "exr")]
44    OpenExr,
45    #[cfg(feature = "hdr")]
46    Hdr,
47    #[cfg(feature = "ico")]
48    Ico,
49    #[cfg(feature = "jpeg")]
50    Jpeg,
51    #[cfg(feature = "ktx2")]
52    Ktx2,
53    #[cfg(feature = "png")]
54    Png,
55    #[cfg(feature = "pnm")]
56    Pnm,
57    #[cfg(feature = "qoi")]
58    Qoi,
59    #[cfg(feature = "tga")]
60    Tga,
61    #[cfg(feature = "tiff")]
62    Tiff,
63    #[cfg(feature = "webp")]
64    WebP,
65}
66
67macro_rules! feature_gate {
68    ($feature: tt, $value: ident) => {{
69        #[cfg(not(feature = $feature))]
70        {
71            bevy_utils::tracing::warn!("feature \"{}\" is not enabled", $feature);
72            return None;
73        }
74        #[cfg(feature = $feature)]
75        ImageFormat::$value
76    }};
77}
78
79impl ImageFormat {
80    /// Gets the file extensions for a given format.
81    pub const fn to_file_extensions(&self) -> &'static [&'static str] {
82        match self {
83            #[cfg(feature = "basis-universal")]
84            ImageFormat::Basis => &["basis"],
85            #[cfg(feature = "bmp")]
86            ImageFormat::Bmp => &["bmp"],
87            #[cfg(feature = "dds")]
88            ImageFormat::Dds => &["dds"],
89            #[cfg(feature = "ff")]
90            ImageFormat::Farbfeld => &["ff", "farbfeld"],
91            #[cfg(feature = "gif")]
92            ImageFormat::Gif => &["gif"],
93            #[cfg(feature = "exr")]
94            ImageFormat::OpenExr => &["exr"],
95            #[cfg(feature = "hdr")]
96            ImageFormat::Hdr => &["hdr"],
97            #[cfg(feature = "ico")]
98            ImageFormat::Ico => &["ico"],
99            #[cfg(feature = "jpeg")]
100            ImageFormat::Jpeg => &["jpg", "jpeg"],
101            #[cfg(feature = "ktx2")]
102            ImageFormat::Ktx2 => &["ktx2"],
103            #[cfg(feature = "pnm")]
104            ImageFormat::Pnm => &["pam", "pbm", "pgm", "ppm"],
105            #[cfg(feature = "png")]
106            ImageFormat::Png => &["png"],
107            #[cfg(feature = "qoi")]
108            ImageFormat::Qoi => &["qoi"],
109            #[cfg(feature = "tga")]
110            ImageFormat::Tga => &["tga"],
111            #[cfg(feature = "tiff")]
112            ImageFormat::Tiff => &["tif", "tiff"],
113            #[cfg(feature = "webp")]
114            ImageFormat::WebP => &["webp"],
115            // FIXME: https://github.com/rust-lang/rust/issues/129031
116            #[allow(unreachable_patterns)]
117            _ => &[],
118        }
119    }
120
121    /// Gets the MIME types for a given format.
122    ///
123    /// If a format doesn't have any dedicated MIME types, this list will be empty.
124    pub const fn to_mime_types(&self) -> &'static [&'static str] {
125        match self {
126            #[cfg(feature = "basis-universal")]
127            ImageFormat::Basis => &["image/basis", "image/x-basis"],
128            #[cfg(feature = "bmp")]
129            ImageFormat::Bmp => &["image/bmp", "image/x-bmp"],
130            #[cfg(feature = "dds")]
131            ImageFormat::Dds => &["image/vnd-ms.dds"],
132            #[cfg(feature = "hdr")]
133            ImageFormat::Hdr => &["image/vnd.radiance"],
134            #[cfg(feature = "gif")]
135            ImageFormat::Gif => &["image/gif"],
136            #[cfg(feature = "ff")]
137            ImageFormat::Farbfeld => &[],
138            #[cfg(feature = "ico")]
139            ImageFormat::Ico => &["image/x-icon"],
140            #[cfg(feature = "jpeg")]
141            ImageFormat::Jpeg => &["image/jpeg"],
142            #[cfg(feature = "ktx2")]
143            ImageFormat::Ktx2 => &["image/ktx2"],
144            #[cfg(feature = "png")]
145            ImageFormat::Png => &["image/png"],
146            #[cfg(feature = "qoi")]
147            ImageFormat::Qoi => &["image/qoi", "image/x-qoi"],
148            #[cfg(feature = "exr")]
149            ImageFormat::OpenExr => &["image/x-exr"],
150            #[cfg(feature = "pnm")]
151            ImageFormat::Pnm => &[
152                "image/x-portable-bitmap",
153                "image/x-portable-graymap",
154                "image/x-portable-pixmap",
155                "image/x-portable-anymap",
156            ],
157            #[cfg(feature = "tga")]
158            ImageFormat::Tga => &["image/x-targa", "image/x-tga"],
159            #[cfg(feature = "tiff")]
160            ImageFormat::Tiff => &["image/tiff"],
161            #[cfg(feature = "webp")]
162            ImageFormat::WebP => &["image/webp"],
163            // FIXME: https://github.com/rust-lang/rust/issues/129031
164            #[allow(unreachable_patterns)]
165            _ => &[],
166        }
167    }
168
169    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
170        #[allow(unreachable_code)]
171        Some(match mime_type.to_ascii_lowercase().as_str() {
172            // note: farbfeld does not have a MIME type
173            "image/basis" | "image/x-basis" => feature_gate!("basis-universal", Basis),
174            "image/bmp" | "image/x-bmp" => feature_gate!("bmp", Bmp),
175            "image/vnd-ms.dds" => feature_gate!("dds", Dds),
176            "image/vnd.radiance" => feature_gate!("hdr", Hdr),
177            "image/gif" => feature_gate!("gif", Gif),
178            "image/x-icon" => feature_gate!("ico", Ico),
179            "image/jpeg" => feature_gate!("jpeg", Jpeg),
180            "image/ktx2" => feature_gate!("ktx2", Ktx2),
181            "image/png" => feature_gate!("png", Png),
182            "image/qoi" | "image/x-qoi" => feature_gate!("qoi", Qoi),
183            "image/x-exr" => feature_gate!("exr", OpenExr),
184            "image/x-portable-bitmap"
185            | "image/x-portable-graymap"
186            | "image/x-portable-pixmap"
187            | "image/x-portable-anymap" => feature_gate!("pnm", Pnm),
188            "image/x-targa" | "image/x-tga" => feature_gate!("tga", Tga),
189            "image/tiff" => feature_gate!("tiff", Tiff),
190            "image/webp" => feature_gate!("webp", WebP),
191            _ => return None,
192        })
193    }
194
195    pub fn from_extension(extension: &str) -> Option<Self> {
196        #[allow(unreachable_code)]
197        Some(match extension.to_ascii_lowercase().as_str() {
198            "basis" => feature_gate!("basis-universal", Basis),
199            "bmp" => feature_gate!("bmp", Bmp),
200            "dds" => feature_gate!("dds", Dds),
201            "ff" | "farbfeld" => feature_gate!("ff", Farbfeld),
202            "gif" => feature_gate!("gif", Gif),
203            "exr" => feature_gate!("exr", OpenExr),
204            "hdr" => feature_gate!("hdr", Hdr),
205            "ico" => feature_gate!("ico", Ico),
206            "jpg" | "jpeg" => feature_gate!("jpeg", Jpeg),
207            "ktx2" => feature_gate!("ktx2", Ktx2),
208            "pam" | "pbm" | "pgm" | "ppm" => feature_gate!("pnm", Pnm),
209            "png" => feature_gate!("png", Png),
210            "qoi" => feature_gate!("qoi", Qoi),
211            "tga" => feature_gate!("tga", Tga),
212            "tif" | "tiff" => feature_gate!("tiff", Tiff),
213            "webp" => feature_gate!("webp", WebP),
214            _ => return None,
215        })
216    }
217
218    pub fn as_image_crate_format(&self) -> Option<image::ImageFormat> {
219        #[allow(unreachable_code)]
220        Some(match self {
221            #[cfg(feature = "bmp")]
222            ImageFormat::Bmp => image::ImageFormat::Bmp,
223            #[cfg(feature = "dds")]
224            ImageFormat::Dds => image::ImageFormat::Dds,
225            #[cfg(feature = "ff")]
226            ImageFormat::Farbfeld => image::ImageFormat::Farbfeld,
227            #[cfg(feature = "gif")]
228            ImageFormat::Gif => image::ImageFormat::Gif,
229            #[cfg(feature = "exr")]
230            ImageFormat::OpenExr => image::ImageFormat::OpenExr,
231            #[cfg(feature = "hdr")]
232            ImageFormat::Hdr => image::ImageFormat::Hdr,
233            #[cfg(feature = "ico")]
234            ImageFormat::Ico => image::ImageFormat::Ico,
235            #[cfg(feature = "jpeg")]
236            ImageFormat::Jpeg => image::ImageFormat::Jpeg,
237            #[cfg(feature = "png")]
238            ImageFormat::Png => image::ImageFormat::Png,
239            #[cfg(feature = "pnm")]
240            ImageFormat::Pnm => image::ImageFormat::Pnm,
241            #[cfg(feature = "qoi")]
242            ImageFormat::Qoi => image::ImageFormat::Qoi,
243            #[cfg(feature = "tga")]
244            ImageFormat::Tga => image::ImageFormat::Tga,
245            #[cfg(feature = "tiff")]
246            ImageFormat::Tiff => image::ImageFormat::Tiff,
247            #[cfg(feature = "webp")]
248            ImageFormat::WebP => image::ImageFormat::WebP,
249            #[cfg(feature = "basis-universal")]
250            ImageFormat::Basis => return None,
251            #[cfg(feature = "ktx2")]
252            ImageFormat::Ktx2 => return None,
253            // FIXME: https://github.com/rust-lang/rust/issues/129031
254            #[allow(unreachable_patterns)]
255            _ => return None,
256        })
257    }
258
259    pub fn from_image_crate_format(format: image::ImageFormat) -> Option<ImageFormat> {
260        #[allow(unreachable_code)]
261        Some(match format {
262            image::ImageFormat::Bmp => feature_gate!("bmp", Bmp),
263            image::ImageFormat::Dds => feature_gate!("dds", Dds),
264            image::ImageFormat::Farbfeld => feature_gate!("ff", Farbfeld),
265            image::ImageFormat::Gif => feature_gate!("gif", Gif),
266            image::ImageFormat::OpenExr => feature_gate!("exr", OpenExr),
267            image::ImageFormat::Hdr => feature_gate!("hdr", Hdr),
268            image::ImageFormat::Ico => feature_gate!("ico", Ico),
269            image::ImageFormat::Jpeg => feature_gate!("jpeg", Jpeg),
270            image::ImageFormat::Png => feature_gate!("png", Png),
271            image::ImageFormat::Pnm => feature_gate!("pnm", Pnm),
272            image::ImageFormat::Qoi => feature_gate!("qoi", Qoi),
273            image::ImageFormat::Tga => feature_gate!("tga", Tga),
274            image::ImageFormat::Tiff => feature_gate!("tiff", Tiff),
275            image::ImageFormat::WebP => feature_gate!("webp", WebP),
276            _ => return None,
277        })
278    }
279}
280
281#[derive(Asset, Reflect, Debug, Clone)]
282#[reflect(opaque)]
283#[reflect(Default, Debug)]
284pub struct Image {
285    pub data: Vec<u8>,
286    // TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors
287    pub texture_descriptor: wgpu::TextureDescriptor<'static>,
288    /// The [`ImageSampler`] to use during rendering.
289    pub sampler: ImageSampler,
290    pub texture_view_descriptor: Option<TextureViewDescriptor<'static>>,
291    pub asset_usage: RenderAssetUsages,
292}
293
294/// Used in [`Image`], this determines what image sampler to use when rendering. The default setting,
295/// [`ImageSampler::Default`], will read the sampler from the `ImagePlugin` at setup.
296/// Setting this to [`ImageSampler::Descriptor`] will override the global default descriptor for this [`Image`].
297#[derive(Debug, Default, Clone, Serialize, Deserialize)]
298pub enum ImageSampler {
299    /// Default image sampler, derived from the `ImagePlugin` setup.
300    #[default]
301    Default,
302    /// Custom sampler for this image which will override global default.
303    Descriptor(ImageSamplerDescriptor),
304}
305
306impl ImageSampler {
307    /// Returns an image sampler with [`ImageFilterMode::Linear`] min and mag filters
308    #[inline]
309    pub fn linear() -> ImageSampler {
310        ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
311    }
312
313    /// Returns an image sampler with [`ImageFilterMode::Nearest`] min and mag filters
314    #[inline]
315    pub fn nearest() -> ImageSampler {
316        ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
317    }
318
319    /// Initialize the descriptor if it is not already initialized.
320    ///
321    /// Descriptor is typically initialized by Bevy when the image is loaded,
322    /// so this is convenient shortcut for updating the descriptor.
323    pub fn get_or_init_descriptor(&mut self) -> &mut ImageSamplerDescriptor {
324        match self {
325            ImageSampler::Default => {
326                *self = ImageSampler::Descriptor(ImageSamplerDescriptor::default());
327                match self {
328                    ImageSampler::Descriptor(descriptor) => descriptor,
329                    _ => unreachable!(),
330                }
331            }
332            ImageSampler::Descriptor(descriptor) => descriptor,
333        }
334    }
335}
336
337/// How edges should be handled in texture addressing.
338///
339/// See [`ImageSamplerDescriptor`] for information how to configure this.
340///
341/// This type mirrors [`wgpu::AddressMode`].
342#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
343pub enum ImageAddressMode {
344    /// Clamp the value to the edge of the texture.
345    ///
346    /// -0.25 -> 0.0
347    /// 1.25  -> 1.0
348    #[default]
349    ClampToEdge,
350    /// Repeat the texture in a tiling fashion.
351    ///
352    /// -0.25 -> 0.75
353    /// 1.25 -> 0.25
354    Repeat,
355    /// Repeat the texture, mirroring it every repeat.
356    ///
357    /// -0.25 -> 0.25
358    /// 1.25 -> 0.75
359    MirrorRepeat,
360    /// Clamp the value to the border of the texture
361    /// Requires the wgpu feature [`wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER`].
362    ///
363    /// -0.25 -> border
364    /// 1.25 -> border
365    ClampToBorder,
366}
367
368/// Texel mixing mode when sampling between texels.
369///
370/// This type mirrors [`wgpu::FilterMode`].
371#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
372pub enum ImageFilterMode {
373    /// Nearest neighbor sampling.
374    ///
375    /// This creates a pixelated effect when used as a mag filter.
376    #[default]
377    Nearest,
378    /// Linear Interpolation.
379    ///
380    /// This makes textures smooth but blurry when used as a mag filter.
381    Linear,
382}
383
384/// Comparison function used for depth and stencil operations.
385///
386/// This type mirrors [`wgpu::CompareFunction`].
387#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
388pub enum ImageCompareFunction {
389    /// Function never passes
390    Never,
391    /// Function passes if new value less than existing value
392    Less,
393    /// Function passes if new value is equal to existing value. When using
394    /// this compare function, make sure to mark your Vertex Shader's `@builtin(position)`
395    /// output as `@invariant` to prevent artifacting.
396    Equal,
397    /// Function passes if new value is less than or equal to existing value
398    LessEqual,
399    /// Function passes if new value is greater than existing value
400    Greater,
401    /// Function passes if new value is not equal to existing value. When using
402    /// this compare function, make sure to mark your Vertex Shader's `@builtin(position)`
403    /// output as `@invariant` to prevent artifacting.
404    NotEqual,
405    /// Function passes if new value is greater than or equal to existing value
406    GreaterEqual,
407    /// Function always passes
408    Always,
409}
410
411/// Color variation to use when the sampler addressing mode is [`ImageAddressMode::ClampToBorder`].
412///
413/// This type mirrors [`wgpu::SamplerBorderColor`].
414#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
415pub enum ImageSamplerBorderColor {
416    /// RGBA color `[0, 0, 0, 0]`.
417    TransparentBlack,
418    /// RGBA color `[0, 0, 0, 1]`.
419    OpaqueBlack,
420    /// RGBA color `[1, 1, 1, 1]`.
421    OpaqueWhite,
422    /// On the Metal wgpu backend, this is equivalent to [`Self::TransparentBlack`] for
423    /// textures that have an alpha component, and equivalent to [`Self::OpaqueBlack`]
424    /// for textures that do not have an alpha component. On other backends,
425    /// this is equivalent to [`Self::TransparentBlack`]. Requires
426    /// [`wgpu::Features::ADDRESS_MODE_CLAMP_TO_ZERO`]. Not supported on the web.
427    Zero,
428}
429
430/// Indicates to an `ImageLoader` how an [`Image`] should be sampled.
431///
432/// As this type is part of the `ImageLoaderSettings`,
433/// it will be serialized to an image asset `.meta` file which might require a migration in case of
434/// a breaking change.
435///
436/// This types mirrors [`wgpu::SamplerDescriptor`], but that might change in future versions.
437#[derive(Clone, Debug, Serialize, Deserialize)]
438pub struct ImageSamplerDescriptor {
439    pub label: Option<String>,
440    /// How to deal with out of bounds accesses in the u (i.e. x) direction.
441    pub address_mode_u: ImageAddressMode,
442    /// How to deal with out of bounds accesses in the v (i.e. y) direction.
443    pub address_mode_v: ImageAddressMode,
444    /// How to deal with out of bounds accesses in the w (i.e. z) direction.
445    pub address_mode_w: ImageAddressMode,
446    /// How to filter the texture when it needs to be magnified (made larger).
447    pub mag_filter: ImageFilterMode,
448    /// How to filter the texture when it needs to be minified (made smaller).
449    pub min_filter: ImageFilterMode,
450    /// How to filter between mip map levels
451    pub mipmap_filter: ImageFilterMode,
452    /// Minimum level of detail (i.e. mip level) to use.
453    pub lod_min_clamp: f32,
454    /// Maximum level of detail (i.e. mip level) to use.
455    pub lod_max_clamp: f32,
456    /// If this is enabled, this is a comparison sampler using the given comparison function.
457    pub compare: Option<ImageCompareFunction>,
458    /// Must be at least 1. If this is not 1, all filter modes must be linear.
459    pub anisotropy_clamp: u16,
460    /// Border color to use when `address_mode` is [`ImageAddressMode::ClampToBorder`].
461    pub border_color: Option<ImageSamplerBorderColor>,
462}
463
464impl Default for ImageSamplerDescriptor {
465    fn default() -> Self {
466        Self {
467            address_mode_u: Default::default(),
468            address_mode_v: Default::default(),
469            address_mode_w: Default::default(),
470            mag_filter: Default::default(),
471            min_filter: Default::default(),
472            mipmap_filter: Default::default(),
473            lod_min_clamp: 0.0,
474            lod_max_clamp: 32.0,
475            compare: None,
476            anisotropy_clamp: 1,
477            border_color: None,
478            label: None,
479        }
480    }
481}
482
483impl ImageSamplerDescriptor {
484    /// Returns a sampler descriptor with [`Linear`](ImageFilterMode::Linear) min and mag filters
485    #[inline]
486    pub fn linear() -> ImageSamplerDescriptor {
487        ImageSamplerDescriptor {
488            mag_filter: ImageFilterMode::Linear,
489            min_filter: ImageFilterMode::Linear,
490            mipmap_filter: ImageFilterMode::Linear,
491            ..Default::default()
492        }
493    }
494
495    /// Returns a sampler descriptor with [`Nearest`](ImageFilterMode::Nearest) min and mag filters
496    #[inline]
497    pub fn nearest() -> ImageSamplerDescriptor {
498        ImageSamplerDescriptor {
499            mag_filter: ImageFilterMode::Nearest,
500            min_filter: ImageFilterMode::Nearest,
501            mipmap_filter: ImageFilterMode::Nearest,
502            ..Default::default()
503        }
504    }
505
506    pub fn as_wgpu(&self) -> wgpu::SamplerDescriptor {
507        wgpu::SamplerDescriptor {
508            label: self.label.as_deref(),
509            address_mode_u: self.address_mode_u.into(),
510            address_mode_v: self.address_mode_v.into(),
511            address_mode_w: self.address_mode_w.into(),
512            mag_filter: self.mag_filter.into(),
513            min_filter: self.min_filter.into(),
514            mipmap_filter: self.mipmap_filter.into(),
515            lod_min_clamp: self.lod_min_clamp,
516            lod_max_clamp: self.lod_max_clamp,
517            compare: self.compare.map(Into::into),
518            anisotropy_clamp: self.anisotropy_clamp,
519            border_color: self.border_color.map(Into::into),
520        }
521    }
522}
523
524impl From<ImageAddressMode> for wgpu::AddressMode {
525    fn from(value: ImageAddressMode) -> Self {
526        match value {
527            ImageAddressMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
528            ImageAddressMode::Repeat => wgpu::AddressMode::Repeat,
529            ImageAddressMode::MirrorRepeat => wgpu::AddressMode::MirrorRepeat,
530            ImageAddressMode::ClampToBorder => wgpu::AddressMode::ClampToBorder,
531        }
532    }
533}
534
535impl From<ImageFilterMode> for wgpu::FilterMode {
536    fn from(value: ImageFilterMode) -> Self {
537        match value {
538            ImageFilterMode::Nearest => wgpu::FilterMode::Nearest,
539            ImageFilterMode::Linear => wgpu::FilterMode::Linear,
540        }
541    }
542}
543
544impl From<ImageCompareFunction> for wgpu::CompareFunction {
545    fn from(value: ImageCompareFunction) -> Self {
546        match value {
547            ImageCompareFunction::Never => wgpu::CompareFunction::Never,
548            ImageCompareFunction::Less => wgpu::CompareFunction::Less,
549            ImageCompareFunction::Equal => wgpu::CompareFunction::Equal,
550            ImageCompareFunction::LessEqual => wgpu::CompareFunction::LessEqual,
551            ImageCompareFunction::Greater => wgpu::CompareFunction::Greater,
552            ImageCompareFunction::NotEqual => wgpu::CompareFunction::NotEqual,
553            ImageCompareFunction::GreaterEqual => wgpu::CompareFunction::GreaterEqual,
554            ImageCompareFunction::Always => wgpu::CompareFunction::Always,
555        }
556    }
557}
558
559impl From<ImageSamplerBorderColor> for wgpu::SamplerBorderColor {
560    fn from(value: ImageSamplerBorderColor) -> Self {
561        match value {
562            ImageSamplerBorderColor::TransparentBlack => wgpu::SamplerBorderColor::TransparentBlack,
563            ImageSamplerBorderColor::OpaqueBlack => wgpu::SamplerBorderColor::OpaqueBlack,
564            ImageSamplerBorderColor::OpaqueWhite => wgpu::SamplerBorderColor::OpaqueWhite,
565            ImageSamplerBorderColor::Zero => wgpu::SamplerBorderColor::Zero,
566        }
567    }
568}
569
570impl From<wgpu::AddressMode> for ImageAddressMode {
571    fn from(value: wgpu::AddressMode) -> Self {
572        match value {
573            wgpu::AddressMode::ClampToEdge => ImageAddressMode::ClampToEdge,
574            wgpu::AddressMode::Repeat => ImageAddressMode::Repeat,
575            wgpu::AddressMode::MirrorRepeat => ImageAddressMode::MirrorRepeat,
576            wgpu::AddressMode::ClampToBorder => ImageAddressMode::ClampToBorder,
577        }
578    }
579}
580
581impl From<wgpu::FilterMode> for ImageFilterMode {
582    fn from(value: wgpu::FilterMode) -> Self {
583        match value {
584            wgpu::FilterMode::Nearest => ImageFilterMode::Nearest,
585            wgpu::FilterMode::Linear => ImageFilterMode::Linear,
586        }
587    }
588}
589
590impl From<wgpu::CompareFunction> for ImageCompareFunction {
591    fn from(value: wgpu::CompareFunction) -> Self {
592        match value {
593            wgpu::CompareFunction::Never => ImageCompareFunction::Never,
594            wgpu::CompareFunction::Less => ImageCompareFunction::Less,
595            wgpu::CompareFunction::Equal => ImageCompareFunction::Equal,
596            wgpu::CompareFunction::LessEqual => ImageCompareFunction::LessEqual,
597            wgpu::CompareFunction::Greater => ImageCompareFunction::Greater,
598            wgpu::CompareFunction::NotEqual => ImageCompareFunction::NotEqual,
599            wgpu::CompareFunction::GreaterEqual => ImageCompareFunction::GreaterEqual,
600            wgpu::CompareFunction::Always => ImageCompareFunction::Always,
601        }
602    }
603}
604
605impl From<wgpu::SamplerBorderColor> for ImageSamplerBorderColor {
606    fn from(value: wgpu::SamplerBorderColor) -> Self {
607        match value {
608            wgpu::SamplerBorderColor::TransparentBlack => ImageSamplerBorderColor::TransparentBlack,
609            wgpu::SamplerBorderColor::OpaqueBlack => ImageSamplerBorderColor::OpaqueBlack,
610            wgpu::SamplerBorderColor::OpaqueWhite => ImageSamplerBorderColor::OpaqueWhite,
611            wgpu::SamplerBorderColor::Zero => ImageSamplerBorderColor::Zero,
612        }
613    }
614}
615
616impl<'a> From<wgpu::SamplerDescriptor<'a>> for ImageSamplerDescriptor {
617    fn from(value: wgpu::SamplerDescriptor) -> Self {
618        ImageSamplerDescriptor {
619            label: value.label.map(ToString::to_string),
620            address_mode_u: value.address_mode_u.into(),
621            address_mode_v: value.address_mode_v.into(),
622            address_mode_w: value.address_mode_w.into(),
623            mag_filter: value.mag_filter.into(),
624            min_filter: value.min_filter.into(),
625            mipmap_filter: value.mipmap_filter.into(),
626            lod_min_clamp: value.lod_min_clamp,
627            lod_max_clamp: value.lod_max_clamp,
628            compare: value.compare.map(Into::into),
629            anisotropy_clamp: value.anisotropy_clamp,
630            border_color: value.border_color.map(Into::into),
631        }
632    }
633}
634
635impl Default for Image {
636    /// default is a 1x1x1 all '1.0' texture
637    fn default() -> Self {
638        let format = TextureFormat::bevy_default();
639        let data = vec![255; format.pixel_size()];
640        Image {
641            data,
642            texture_descriptor: wgpu::TextureDescriptor {
643                size: Extent3d {
644                    width: 1,
645                    height: 1,
646                    depth_or_array_layers: 1,
647                },
648                format,
649                dimension: TextureDimension::D2,
650                label: None,
651                mip_level_count: 1,
652                sample_count: 1,
653                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
654                view_formats: &[],
655            },
656            sampler: ImageSampler::Default,
657            texture_view_descriptor: None,
658            asset_usage: RenderAssetUsages::default(),
659        }
660    }
661}
662
663impl Image {
664    /// Creates a new image from raw binary data and the corresponding metadata.
665    ///
666    /// # Panics
667    /// Panics if the length of the `data`, volume of the `size` and the size of the `format`
668    /// do not match.
669    pub fn new(
670        size: Extent3d,
671        dimension: TextureDimension,
672        data: Vec<u8>,
673        format: TextureFormat,
674        asset_usage: RenderAssetUsages,
675    ) -> Self {
676        debug_assert_eq!(
677            size.volume() * format.pixel_size(),
678            data.len(),
679            "Pixel data, size and format have to match",
680        );
681        let mut image = Self {
682            data,
683            ..Default::default()
684        };
685        image.texture_descriptor.dimension = dimension;
686        image.texture_descriptor.size = size;
687        image.texture_descriptor.format = format;
688        image.asset_usage = asset_usage;
689        image
690    }
691
692    /// A transparent white 1x1x1 image.
693    ///
694    /// Contrast to [`Image::default`], which is opaque.
695    pub fn transparent() -> Image {
696        // We rely on the default texture format being RGBA8UnormSrgb
697        // when constructing a transparent color from bytes.
698        // If this changes, this function will need to be updated.
699        let format = TextureFormat::bevy_default();
700        debug_assert!(format.pixel_size() == 4);
701        let data = vec![255, 255, 255, 0];
702        Image {
703            data,
704            texture_descriptor: wgpu::TextureDescriptor {
705                size: Extent3d {
706                    width: 1,
707                    height: 1,
708                    depth_or_array_layers: 1,
709                },
710                format,
711                dimension: TextureDimension::D2,
712                label: None,
713                mip_level_count: 1,
714                sample_count: 1,
715                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
716                view_formats: &[],
717            },
718            sampler: ImageSampler::Default,
719            texture_view_descriptor: None,
720            asset_usage: RenderAssetUsages::default(),
721        }
722    }
723
724    /// Creates a new image from raw binary data and the corresponding metadata, by filling
725    /// the image data with the `pixel` data repeated multiple times.
726    ///
727    /// # Panics
728    /// Panics if the size of the `format` is not a multiple of the length of the `pixel` data.
729    pub fn new_fill(
730        size: Extent3d,
731        dimension: TextureDimension,
732        pixel: &[u8],
733        format: TextureFormat,
734        asset_usage: RenderAssetUsages,
735    ) -> Self {
736        let mut value = Image::default();
737        value.texture_descriptor.format = format;
738        value.texture_descriptor.dimension = dimension;
739        value.asset_usage = asset_usage;
740        value.resize(size);
741
742        debug_assert_eq!(
743            pixel.len() % format.pixel_size(),
744            0,
745            "Must not have incomplete pixel data (pixel size is {}B).",
746            format.pixel_size(),
747        );
748        debug_assert!(
749            pixel.len() <= value.data.len(),
750            "Fill data must fit within pixel buffer (expected {}B).",
751            value.data.len(),
752        );
753
754        for current_pixel in value.data.chunks_exact_mut(pixel.len()) {
755            current_pixel.copy_from_slice(pixel);
756        }
757        value
758    }
759
760    /// Returns the width of a 2D image.
761    #[inline]
762    pub fn width(&self) -> u32 {
763        self.texture_descriptor.size.width
764    }
765
766    /// Returns the height of a 2D image.
767    #[inline]
768    pub fn height(&self) -> u32 {
769        self.texture_descriptor.size.height
770    }
771
772    /// Returns the aspect ratio (width / height) of a 2D image.
773    #[inline]
774    pub fn aspect_ratio(&self) -> AspectRatio {
775        AspectRatio::try_from_pixels(self.width(), self.height()).expect(
776            "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
777        )
778    }
779
780    /// Returns the size of a 2D image as f32.
781    #[inline]
782    pub fn size_f32(&self) -> Vec2 {
783        Vec2::new(self.width() as f32, self.height() as f32)
784    }
785
786    /// Returns the size of a 2D image.
787    #[inline]
788    pub fn size(&self) -> UVec2 {
789        UVec2::new(self.width(), self.height())
790    }
791
792    /// Resizes the image to the new size, by removing information or appending 0 to the `data`.
793    /// Does not properly resize the contents of the image, but only its internal `data` buffer.
794    pub fn resize(&mut self, size: Extent3d) {
795        self.texture_descriptor.size = size;
796        self.data.resize(
797            size.volume() * self.texture_descriptor.format.pixel_size(),
798            0,
799        );
800    }
801
802    /// Changes the `size`, asserting that the total number of data elements (pixels) remains the
803    /// same.
804    ///
805    /// # Panics
806    /// Panics if the `new_size` does not have the same volume as to old one.
807    pub fn reinterpret_size(&mut self, new_size: Extent3d) {
808        assert_eq!(
809            new_size.volume(),
810            self.texture_descriptor.size.volume(),
811            "Incompatible sizes: old = {:?} new = {:?}",
812            self.texture_descriptor.size,
813            new_size
814        );
815
816        self.texture_descriptor.size = new_size;
817    }
818
819    /// Takes a 2D image containing vertically stacked images of the same size, and reinterprets
820    /// it as a 2D array texture, where each of the stacked images becomes one layer of the
821    /// array. This is primarily for use with the `texture2DArray` shader uniform type.
822    ///
823    /// # Panics
824    /// Panics if the texture is not 2D, has more than one layers or is not evenly dividable into
825    /// the `layers`.
826    pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) {
827        // Must be a stacked image, and the height must be divisible by layers.
828        assert_eq!(self.texture_descriptor.dimension, TextureDimension::D2);
829        assert_eq!(self.texture_descriptor.size.depth_or_array_layers, 1);
830        assert_eq!(self.height() % layers, 0);
831
832        self.reinterpret_size(Extent3d {
833            width: self.width(),
834            height: self.height() / layers,
835            depth_or_array_layers: layers,
836        });
837    }
838
839    /// Convert a texture from a format to another. Only a few formats are
840    /// supported as input and output:
841    /// - `TextureFormat::R8Unorm`
842    /// - `TextureFormat::Rg8Unorm`
843    /// - `TextureFormat::Rgba8UnormSrgb`
844    ///
845    /// To get [`Image`] as a [`image::DynamicImage`] see:
846    /// [`Image::try_into_dynamic`].
847    pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
848        self.clone()
849            .try_into_dynamic()
850            .ok()
851            .and_then(|img| match new_format {
852                TextureFormat::R8Unorm => {
853                    Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
854                }
855                TextureFormat::Rg8Unorm => Some((
856                    image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
857                    false,
858                )),
859                TextureFormat::Rgba8UnormSrgb => {
860                    Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
861                }
862                _ => None,
863            })
864            .map(|(dyn_img, is_srgb)| Self::from_dynamic(dyn_img, is_srgb, self.asset_usage))
865    }
866
867    /// Load a bytes buffer in a [`Image`], according to type `image_type`, using the `image`
868    /// crate
869    pub fn from_buffer(
870        #[cfg(all(debug_assertions, feature = "dds"))] name: String,
871        buffer: &[u8],
872        image_type: ImageType,
873        #[allow(unused_variables)] supported_compressed_formats: CompressedImageFormats,
874        is_srgb: bool,
875        image_sampler: ImageSampler,
876        asset_usage: RenderAssetUsages,
877    ) -> Result<Image, TextureError> {
878        let format = image_type.to_image_format()?;
879
880        // Load the image in the expected format.
881        // Some formats like PNG allow for R or RG textures too, so the texture
882        // format needs to be determined. For RGB textures an alpha channel
883        // needs to be added, so the image data needs to be converted in those
884        // cases.
885
886        let mut image = match format {
887            #[cfg(feature = "basis-universal")]
888            ImageFormat::Basis => {
889                basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
890            }
891            #[cfg(feature = "dds")]
892            ImageFormat::Dds => dds_buffer_to_image(
893                #[cfg(debug_assertions)]
894                name,
895                buffer,
896                supported_compressed_formats,
897                is_srgb,
898            )?,
899            #[cfg(feature = "ktx2")]
900            ImageFormat::Ktx2 => {
901                ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
902            }
903            #[allow(unreachable_patterns)]
904            _ => {
905                let image_crate_format = format
906                    .as_image_crate_format()
907                    .ok_or_else(|| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?;
908                let mut reader = image::ImageReader::new(std::io::Cursor::new(buffer));
909                reader.set_format(image_crate_format);
910                reader.no_limits();
911                let dyn_img = reader.decode()?;
912                Self::from_dynamic(dyn_img, is_srgb, asset_usage)
913            }
914        };
915        image.sampler = image_sampler;
916        Ok(image)
917    }
918
919    /// Whether the texture format is compressed or uncompressed
920    pub fn is_compressed(&self) -> bool {
921        let format_description = self.texture_descriptor.format;
922        format_description
923            .required_features()
924            .contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC)
925            || format_description
926                .required_features()
927                .contains(wgpu::Features::TEXTURE_COMPRESSION_BC)
928            || format_description
929                .required_features()
930                .contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2)
931    }
932
933    /// Compute the byte offset where the data of a specific pixel is stored
934    ///
935    /// Returns None if the provided coordinates are out of bounds.
936    ///
937    /// For 2D textures, Z is ignored. For 1D textures, Y and Z are ignored.
938    #[inline(always)]
939    pub fn pixel_data_offset(&self, coords: UVec3) -> Option<usize> {
940        let width = self.texture_descriptor.size.width;
941        let height = self.texture_descriptor.size.height;
942        let depth = self.texture_descriptor.size.depth_or_array_layers;
943
944        let pixel_size = self.texture_descriptor.format.pixel_size();
945        let pixel_offset = match self.texture_descriptor.dimension {
946            TextureDimension::D3 => {
947                if coords.x >= width || coords.y >= height || coords.z >= depth {
948                    return None;
949                }
950                coords.z * height * width + coords.y * width + coords.x
951            }
952            TextureDimension::D2 => {
953                if coords.x >= width || coords.y >= height {
954                    return None;
955                }
956                coords.y * width + coords.x
957            }
958            TextureDimension::D1 => {
959                if coords.x >= width {
960                    return None;
961                }
962                coords.x
963            }
964        };
965
966        Some(pixel_offset as usize * pixel_size)
967    }
968
969    /// Get a reference to the data bytes where a specific pixel's value is stored
970    #[inline(always)]
971    pub fn pixel_bytes(&self, coords: UVec3) -> Option<&[u8]> {
972        let len = self.texture_descriptor.format.pixel_size();
973        self.pixel_data_offset(coords)
974            .map(|start| &self.data[start..(start + len)])
975    }
976
977    /// Get a mutable reference to the data bytes where a specific pixel's value is stored
978    #[inline(always)]
979    pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Option<&mut [u8]> {
980        let len = self.texture_descriptor.format.pixel_size();
981        self.pixel_data_offset(coords)
982            .map(|start| &mut self.data[start..(start + len)])
983    }
984
985    /// Read the color of a specific pixel (1D texture).
986    ///
987    /// See [`get_color_at`](Self::get_color_at) for more details.
988    #[inline(always)]
989    pub fn get_color_at_1d(&self, x: u32) -> Result<Color, TextureAccessError> {
990        if self.texture_descriptor.dimension != TextureDimension::D1 {
991            return Err(TextureAccessError::WrongDimension);
992        }
993        self.get_color_at_internal(UVec3::new(x, 0, 0))
994    }
995
996    /// Read the color of a specific pixel (2D texture).
997    ///
998    /// This function will find the raw byte data of a specific pixel and
999    /// decode it into a user-friendly [`Color`] struct for you.
1000    ///
1001    /// Supports many of the common [`TextureFormat`]s:
1002    ///  - RGBA/BGRA 8-bit unsigned integer, both sRGB and Linear
1003    ///  - 16-bit and 32-bit unsigned integer
1004    ///  - 32-bit float
1005    ///
1006    /// Be careful: as the data is converted to [`Color`] (which uses `f32` internally),
1007    /// there may be issues with precision when using non-float [`TextureFormat`]s.
1008    /// If you read a value you previously wrote using `set_color_at`, it will not match.
1009    /// If you are working with a 32-bit integer [`TextureFormat`], the value will be
1010    /// inaccurate (as `f32` does not have enough bits to represent it exactly).
1011    ///
1012    /// Single channel (R) formats are assumed to represent grayscale, so the value
1013    /// will be copied to all three RGB channels in the resulting [`Color`].
1014    ///
1015    /// Other [`TextureFormat`]s are unsupported, such as:
1016    ///  - block-compressed formats
1017    ///  - non-byte-aligned formats like 10-bit
1018    ///  - 16-bit float formats
1019    ///  - signed integer formats
1020    #[inline(always)]
1021    pub fn get_color_at(&self, x: u32, y: u32) -> Result<Color, TextureAccessError> {
1022        if self.texture_descriptor.dimension != TextureDimension::D2 {
1023            return Err(TextureAccessError::WrongDimension);
1024        }
1025        self.get_color_at_internal(UVec3::new(x, y, 0))
1026    }
1027
1028    /// Read the color of a specific pixel (3D texture).
1029    ///
1030    /// See [`get_color_at`](Self::get_color_at) for more details.
1031    #[inline(always)]
1032    pub fn get_color_at_3d(&self, x: u32, y: u32, z: u32) -> Result<Color, TextureAccessError> {
1033        if self.texture_descriptor.dimension != TextureDimension::D3 {
1034            return Err(TextureAccessError::WrongDimension);
1035        }
1036        self.get_color_at_internal(UVec3::new(x, y, z))
1037    }
1038
1039    /// Change the color of a specific pixel (1D texture).
1040    ///
1041    /// See [`set_color_at`](Self::set_color_at) for more details.
1042    #[inline(always)]
1043    pub fn set_color_at_1d(&mut self, x: u32, color: Color) -> Result<(), TextureAccessError> {
1044        if self.texture_descriptor.dimension != TextureDimension::D1 {
1045            return Err(TextureAccessError::WrongDimension);
1046        }
1047        self.set_color_at_internal(UVec3::new(x, 0, 0), color)
1048    }
1049
1050    /// Change the color of a specific pixel (2D texture).
1051    ///
1052    /// This function will find the raw byte data of a specific pixel and
1053    /// change it according to a [`Color`] you provide. The [`Color`] struct
1054    /// will be encoded into the [`Image`]'s [`TextureFormat`].
1055    ///
1056    /// Supports many of the common [`TextureFormat`]s:
1057    ///  - RGBA/BGRA 8-bit unsigned integer, both sRGB and Linear
1058    ///  - 16-bit and 32-bit unsigned integer (with possibly-limited precision, as [`Color`] uses `f32`)
1059    ///  - 32-bit float
1060    ///
1061    /// Be careful: writing to non-float [`TextureFormat`]s is lossy! The data has to be converted,
1062    /// so if you read it back using `get_color_at`, the `Color` you get will not equal the value
1063    /// you used when writing it using this function.
1064    ///
1065    /// For R and RG formats, only the respective values from the linear RGB [`Color`] will be used.
1066    ///
1067    /// Other [`TextureFormat`]s are unsupported, such as:
1068    ///  - block-compressed formats
1069    ///  - non-byte-aligned formats like 10-bit
1070    ///  - 16-bit float formats
1071    ///  - signed integer formats
1072    #[inline(always)]
1073    pub fn set_color_at(&mut self, x: u32, y: u32, color: Color) -> Result<(), TextureAccessError> {
1074        if self.texture_descriptor.dimension != TextureDimension::D2 {
1075            return Err(TextureAccessError::WrongDimension);
1076        }
1077        self.set_color_at_internal(UVec3::new(x, y, 0), color)
1078    }
1079
1080    /// Change the color of a specific pixel (3D texture).
1081    ///
1082    /// See [`set_color_at`](Self::set_color_at) for more details.
1083    #[inline(always)]
1084    pub fn set_color_at_3d(
1085        &mut self,
1086        x: u32,
1087        y: u32,
1088        z: u32,
1089        color: Color,
1090    ) -> Result<(), TextureAccessError> {
1091        if self.texture_descriptor.dimension != TextureDimension::D3 {
1092            return Err(TextureAccessError::WrongDimension);
1093        }
1094        self.set_color_at_internal(UVec3::new(x, y, z), color)
1095    }
1096
1097    #[inline(always)]
1098    fn get_color_at_internal(&self, coords: UVec3) -> Result<Color, TextureAccessError> {
1099        let Some(bytes) = self.pixel_bytes(coords) else {
1100            return Err(TextureAccessError::OutOfBounds {
1101                x: coords.x,
1102                y: coords.y,
1103                z: coords.z,
1104            });
1105        };
1106
1107        // NOTE: GPUs are always Little Endian.
1108        // Make sure to respect that when we create color values from bytes.
1109        match self.texture_descriptor.format {
1110            TextureFormat::Rgba8UnormSrgb => Ok(Color::srgba(
1111                bytes[0] as f32 / u8::MAX as f32,
1112                bytes[1] as f32 / u8::MAX as f32,
1113                bytes[2] as f32 / u8::MAX as f32,
1114                bytes[3] as f32 / u8::MAX as f32,
1115            )),
1116            TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => Ok(Color::linear_rgba(
1117                bytes[0] as f32 / u8::MAX as f32,
1118                bytes[1] as f32 / u8::MAX as f32,
1119                bytes[2] as f32 / u8::MAX as f32,
1120                bytes[3] as f32 / u8::MAX as f32,
1121            )),
1122            TextureFormat::Bgra8UnormSrgb => Ok(Color::srgba(
1123                bytes[2] as f32 / u8::MAX as f32,
1124                bytes[1] as f32 / u8::MAX as f32,
1125                bytes[0] as f32 / u8::MAX as f32,
1126                bytes[3] as f32 / u8::MAX as f32,
1127            )),
1128            TextureFormat::Bgra8Unorm => Ok(Color::linear_rgba(
1129                bytes[2] as f32 / u8::MAX as f32,
1130                bytes[1] as f32 / u8::MAX as f32,
1131                bytes[0] as f32 / u8::MAX as f32,
1132                bytes[3] as f32 / u8::MAX as f32,
1133            )),
1134            TextureFormat::Rgba32Float => Ok(Color::linear_rgba(
1135                f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1136                f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1137                f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1138                f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1139            )),
1140            TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1141                let (r, g, b, a) = (
1142                    u16::from_le_bytes([bytes[0], bytes[1]]),
1143                    u16::from_le_bytes([bytes[2], bytes[3]]),
1144                    u16::from_le_bytes([bytes[4], bytes[5]]),
1145                    u16::from_le_bytes([bytes[6], bytes[7]]),
1146                );
1147                Ok(Color::linear_rgba(
1148                    // going via f64 to avoid rounding errors with large numbers and division
1149                    (r as f64 / u16::MAX as f64) as f32,
1150                    (g as f64 / u16::MAX as f64) as f32,
1151                    (b as f64 / u16::MAX as f64) as f32,
1152                    (a as f64 / u16::MAX as f64) as f32,
1153                ))
1154            }
1155            TextureFormat::Rgba32Uint => {
1156                let (r, g, b, a) = (
1157                    u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1158                    u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1159                    u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1160                    u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1161                );
1162                Ok(Color::linear_rgba(
1163                    // going via f64 to avoid rounding errors with large numbers and division
1164                    (r as f64 / u32::MAX as f64) as f32,
1165                    (g as f64 / u32::MAX as f64) as f32,
1166                    (b as f64 / u32::MAX as f64) as f32,
1167                    (a as f64 / u32::MAX as f64) as f32,
1168                ))
1169            }
1170            // assume R-only texture format means grayscale (linear)
1171            // copy value to all of RGB in Color
1172            TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1173                let x = bytes[0] as f32 / u8::MAX as f32;
1174                Ok(Color::linear_rgb(x, x, x))
1175            }
1176            TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1177                let x = u16::from_le_bytes([bytes[0], bytes[1]]);
1178                // going via f64 to avoid rounding errors with large numbers and division
1179                let x = (x as f64 / u16::MAX as f64) as f32;
1180                Ok(Color::linear_rgb(x, x, x))
1181            }
1182            TextureFormat::R32Uint => {
1183                let x = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1184                // going via f64 to avoid rounding errors with large numbers and division
1185                let x = (x as f64 / u32::MAX as f64) as f32;
1186                Ok(Color::linear_rgb(x, x, x))
1187            }
1188            TextureFormat::R32Float => {
1189                let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1190                Ok(Color::linear_rgb(x, x, x))
1191            }
1192            TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1193                let r = bytes[0] as f32 / u8::MAX as f32;
1194                let g = bytes[1] as f32 / u8::MAX as f32;
1195                Ok(Color::linear_rgb(r, g, 0.0))
1196            }
1197            TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1198                let r = u16::from_le_bytes([bytes[0], bytes[1]]);
1199                let g = u16::from_le_bytes([bytes[2], bytes[3]]);
1200                // going via f64 to avoid rounding errors with large numbers and division
1201                let r = (r as f64 / u16::MAX as f64) as f32;
1202                let g = (g as f64 / u16::MAX as f64) as f32;
1203                Ok(Color::linear_rgb(r, g, 0.0))
1204            }
1205            TextureFormat::Rg32Uint => {
1206                let r = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1207                let g = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1208                // going via f64 to avoid rounding errors with large numbers and division
1209                let r = (r as f64 / u32::MAX as f64) as f32;
1210                let g = (g as f64 / u32::MAX as f64) as f32;
1211                Ok(Color::linear_rgb(r, g, 0.0))
1212            }
1213            TextureFormat::Rg32Float => {
1214                let r = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1215                let g = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1216                Ok(Color::linear_rgb(r, g, 0.0))
1217            }
1218            _ => Err(TextureAccessError::UnsupportedTextureFormat(
1219                self.texture_descriptor.format,
1220            )),
1221        }
1222    }
1223
1224    #[inline(always)]
1225    fn set_color_at_internal(
1226        &mut self,
1227        coords: UVec3,
1228        color: Color,
1229    ) -> Result<(), TextureAccessError> {
1230        let format = self.texture_descriptor.format;
1231
1232        let Some(bytes) = self.pixel_bytes_mut(coords) else {
1233            return Err(TextureAccessError::OutOfBounds {
1234                x: coords.x,
1235                y: coords.y,
1236                z: coords.z,
1237            });
1238        };
1239
1240        // NOTE: GPUs are always Little Endian.
1241        // Make sure to respect that when we convert color values to bytes.
1242        match format {
1243            TextureFormat::Rgba8UnormSrgb => {
1244                let [r, g, b, a] = Srgba::from(color).to_f32_array();
1245                bytes[0] = (r * u8::MAX as f32) as u8;
1246                bytes[1] = (g * u8::MAX as f32) as u8;
1247                bytes[2] = (b * u8::MAX as f32) as u8;
1248                bytes[3] = (a * u8::MAX as f32) as u8;
1249            }
1250            TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => {
1251                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1252                bytes[0] = (r * u8::MAX as f32) as u8;
1253                bytes[1] = (g * u8::MAX as f32) as u8;
1254                bytes[2] = (b * u8::MAX as f32) as u8;
1255                bytes[3] = (a * u8::MAX as f32) as u8;
1256            }
1257            TextureFormat::Bgra8UnormSrgb => {
1258                let [r, g, b, a] = Srgba::from(color).to_f32_array();
1259                bytes[0] = (b * u8::MAX as f32) as u8;
1260                bytes[1] = (g * u8::MAX as f32) as u8;
1261                bytes[2] = (r * u8::MAX as f32) as u8;
1262                bytes[3] = (a * u8::MAX as f32) as u8;
1263            }
1264            TextureFormat::Bgra8Unorm => {
1265                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1266                bytes[0] = (b * u8::MAX as f32) as u8;
1267                bytes[1] = (g * u8::MAX as f32) as u8;
1268                bytes[2] = (r * u8::MAX as f32) as u8;
1269                bytes[3] = (a * u8::MAX as f32) as u8;
1270            }
1271            TextureFormat::Rgba32Float => {
1272                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1273                bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1274                bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1275                bytes[8..12].copy_from_slice(&f32::to_le_bytes(b));
1276                bytes[12..16].copy_from_slice(&f32::to_le_bytes(a));
1277            }
1278            TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1279                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1280                let [r, g, b, a] = [
1281                    (r * u16::MAX as f32) as u16,
1282                    (g * u16::MAX as f32) as u16,
1283                    (b * u16::MAX as f32) as u16,
1284                    (a * u16::MAX as f32) as u16,
1285                ];
1286                bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1287                bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1288                bytes[4..6].copy_from_slice(&u16::to_le_bytes(b));
1289                bytes[6..8].copy_from_slice(&u16::to_le_bytes(a));
1290            }
1291            TextureFormat::Rgba32Uint => {
1292                let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1293                let [r, g, b, a] = [
1294                    (r * u32::MAX as f32) as u32,
1295                    (g * u32::MAX as f32) as u32,
1296                    (b * u32::MAX as f32) as u32,
1297                    (a * u32::MAX as f32) as u32,
1298                ];
1299                bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1300                bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1301                bytes[8..12].copy_from_slice(&u32::to_le_bytes(b));
1302                bytes[12..16].copy_from_slice(&u32::to_le_bytes(a));
1303            }
1304            TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1305                // Convert to grayscale with minimal loss if color is already gray
1306                let linear = LinearRgba::from(color);
1307                let luminance = Xyza::from(linear).y;
1308                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1309                bytes[0] = (r * u8::MAX as f32) as u8;
1310            }
1311            TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1312                // Convert to grayscale with minimal loss if color is already gray
1313                let linear = LinearRgba::from(color);
1314                let luminance = Xyza::from(linear).y;
1315                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1316                let r = (r * u16::MAX as f32) as u16;
1317                bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1318            }
1319            TextureFormat::R32Uint => {
1320                // Convert to grayscale with minimal loss if color is already gray
1321                let linear = LinearRgba::from(color);
1322                let luminance = Xyza::from(linear).y;
1323                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1324                // go via f64 to avoid imprecision
1325                let r = (r as f64 * u32::MAX as f64) as u32;
1326                bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1327            }
1328            TextureFormat::R32Float => {
1329                // Convert to grayscale with minimal loss if color is already gray
1330                let linear = LinearRgba::from(color);
1331                let luminance = Xyza::from(linear).y;
1332                let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1333                bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1334            }
1335            TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1336                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1337                bytes[0] = (r * u8::MAX as f32) as u8;
1338                bytes[1] = (g * u8::MAX as f32) as u8;
1339            }
1340            TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1341                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1342                let r = (r * u16::MAX as f32) as u16;
1343                let g = (g * u16::MAX as f32) as u16;
1344                bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1345                bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1346            }
1347            TextureFormat::Rg32Uint => {
1348                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1349                // go via f64 to avoid imprecision
1350                let r = (r as f64 * u32::MAX as f64) as u32;
1351                let g = (g as f64 * u32::MAX as f64) as u32;
1352                bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1353                bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1354            }
1355            TextureFormat::Rg32Float => {
1356                let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1357                bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1358                bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1359            }
1360            _ => {
1361                return Err(TextureAccessError::UnsupportedTextureFormat(
1362                    self.texture_descriptor.format,
1363                ));
1364            }
1365        }
1366        Ok(())
1367    }
1368}
1369
1370#[derive(Clone, Copy, Debug)]
1371pub enum DataFormat {
1372    Rgb,
1373    Rgba,
1374    Rrr,
1375    Rrrg,
1376    Rg,
1377}
1378
1379#[derive(Clone, Copy, Debug)]
1380pub enum TranscodeFormat {
1381    Etc1s,
1382    Uastc(DataFormat),
1383    // Has to be transcoded to R8Unorm for use with `wgpu`
1384    R8UnormSrgb,
1385    // Has to be transcoded to R8G8Unorm for use with `wgpu`
1386    Rg8UnormSrgb,
1387    // Has to be transcoded to Rgba8 for use with `wgpu`
1388    Rgb8,
1389}
1390
1391/// An error that occurs when accessing specific pixels in a texture
1392#[derive(Error, Display, Debug)]
1393pub enum TextureAccessError {
1394    #[display("out of bounds (x: {x}, y: {y}, z: {z})")]
1395    OutOfBounds { x: u32, y: u32, z: u32 },
1396    #[display("unsupported texture format: {_0:?}")]
1397    #[error(ignore)]
1398    UnsupportedTextureFormat(TextureFormat),
1399    #[display("attempt to access texture with different dimension")]
1400    WrongDimension,
1401}
1402
1403/// An error that occurs when loading a texture
1404#[derive(Error, Display, Debug, From)]
1405#[error(ignore)]
1406pub enum TextureError {
1407    #[display("invalid image mime type: {_0}")]
1408    #[from(ignore)]
1409    InvalidImageMimeType(String),
1410    #[display("invalid image extension: {_0}")]
1411    #[from(ignore)]
1412    InvalidImageExtension(String),
1413    #[display("failed to load an image: {_0}")]
1414    ImageError(image::ImageError),
1415    #[display("unsupported texture format: {_0}")]
1416    #[from(ignore)]
1417    UnsupportedTextureFormat(String),
1418    #[display("supercompression not supported: {_0}")]
1419    #[from(ignore)]
1420    SuperCompressionNotSupported(String),
1421    #[display("failed to load an image: {_0}")]
1422    #[from(ignore)]
1423    SuperDecompressionError(String),
1424    #[display("invalid data: {_0}")]
1425    #[from(ignore)]
1426    InvalidData(String),
1427    #[display("transcode error: {_0}")]
1428    #[from(ignore)]
1429    TranscodeError(String),
1430    #[display("format requires transcoding: {_0:?}")]
1431    FormatRequiresTranscodingError(TranscodeFormat),
1432    /// Only cubemaps with six faces are supported.
1433    #[display("only cubemaps with six faces are supported")]
1434    IncompleteCubemap,
1435}
1436
1437/// The type of a raw image buffer.
1438#[derive(Debug)]
1439pub enum ImageType<'a> {
1440    /// The mime type of an image, for example `"image/png"`.
1441    MimeType(&'a str),
1442    /// The extension of an image file, for example `"png"`.
1443    Extension(&'a str),
1444    /// The direct format of the image
1445    Format(ImageFormat),
1446}
1447
1448impl<'a> ImageType<'a> {
1449    pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
1450        match self {
1451            ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
1452                .ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
1453            ImageType::Extension(extension) => ImageFormat::from_extension(extension)
1454                .ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
1455            ImageType::Format(format) => Ok(*format),
1456        }
1457    }
1458}
1459
1460/// Used to calculate the volume of an item.
1461pub trait Volume {
1462    fn volume(&self) -> usize;
1463}
1464
1465impl Volume for Extent3d {
1466    /// Calculates the volume of the [`Extent3d`].
1467    fn volume(&self) -> usize {
1468        (self.width * self.height * self.depth_or_array_layers) as usize
1469    }
1470}
1471
1472/// Extends the wgpu [`TextureFormat`] with information about the pixel.
1473pub trait TextureFormatPixelInfo {
1474    /// Returns the size of a pixel in bytes of the format.
1475    fn pixel_size(&self) -> usize;
1476}
1477
1478impl TextureFormatPixelInfo for TextureFormat {
1479    fn pixel_size(&self) -> usize {
1480        let info = self;
1481        match info.block_dimensions() {
1482            (1, 1) => info.block_copy_size(None).unwrap() as usize,
1483            _ => panic!("Using pixel_size for compressed textures is invalid"),
1484        }
1485    }
1486}
1487
1488bitflags::bitflags! {
1489    #[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
1490    #[repr(transparent)]
1491    pub struct CompressedImageFormats: u32 {
1492        const NONE     = 0;
1493        const ASTC_LDR = 1 << 0;
1494        const BC       = 1 << 1;
1495        const ETC2     = 1 << 2;
1496    }
1497}
1498
1499impl CompressedImageFormats {
1500    pub fn from_features(features: wgpu::Features) -> Self {
1501        let mut supported_compressed_formats = Self::default();
1502        if features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC) {
1503            supported_compressed_formats |= Self::ASTC_LDR;
1504        }
1505        if features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) {
1506            supported_compressed_formats |= Self::BC;
1507        }
1508        if features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) {
1509            supported_compressed_formats |= Self::ETC2;
1510        }
1511        supported_compressed_formats
1512    }
1513
1514    pub fn supports(&self, format: TextureFormat) -> bool {
1515        match format {
1516            TextureFormat::Bc1RgbaUnorm
1517            | TextureFormat::Bc1RgbaUnormSrgb
1518            | TextureFormat::Bc2RgbaUnorm
1519            | TextureFormat::Bc2RgbaUnormSrgb
1520            | TextureFormat::Bc3RgbaUnorm
1521            | TextureFormat::Bc3RgbaUnormSrgb
1522            | TextureFormat::Bc4RUnorm
1523            | TextureFormat::Bc4RSnorm
1524            | TextureFormat::Bc5RgUnorm
1525            | TextureFormat::Bc5RgSnorm
1526            | TextureFormat::Bc6hRgbUfloat
1527            | TextureFormat::Bc6hRgbFloat
1528            | TextureFormat::Bc7RgbaUnorm
1529            | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
1530            TextureFormat::Etc2Rgb8Unorm
1531            | TextureFormat::Etc2Rgb8UnormSrgb
1532            | TextureFormat::Etc2Rgb8A1Unorm
1533            | TextureFormat::Etc2Rgb8A1UnormSrgb
1534            | TextureFormat::Etc2Rgba8Unorm
1535            | TextureFormat::Etc2Rgba8UnormSrgb
1536            | TextureFormat::EacR11Unorm
1537            | TextureFormat::EacR11Snorm
1538            | TextureFormat::EacRg11Unorm
1539            | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
1540            TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
1541            _ => true,
1542        }
1543    }
1544}
1545
1546#[cfg(test)]
1547mod test {
1548    use super::*;
1549
1550    #[test]
1551    fn image_size() {
1552        let size = Extent3d {
1553            width: 200,
1554            height: 100,
1555            depth_or_array_layers: 1,
1556        };
1557        let image = Image::new_fill(
1558            size,
1559            TextureDimension::D2,
1560            &[0, 0, 0, 255],
1561            TextureFormat::Rgba8Unorm,
1562            RenderAssetUsages::MAIN_WORLD,
1563        );
1564        assert_eq!(
1565            Vec2::new(size.width as f32, size.height as f32),
1566            image.size_f32()
1567        );
1568    }
1569
1570    #[test]
1571    fn image_default_size() {
1572        let image = Image::default();
1573        assert_eq!(UVec2::ONE, image.size());
1574        assert_eq!(Vec2::ONE, image.size_f32());
1575    }
1576
1577    #[test]
1578    fn on_edge_pixel_is_invalid() {
1579        let image = Image::new_fill(
1580            Extent3d {
1581                width: 5,
1582                height: 10,
1583                depth_or_array_layers: 1,
1584            },
1585            TextureDimension::D2,
1586            &[0, 0, 0, 255],
1587            TextureFormat::Rgba8Unorm,
1588            RenderAssetUsages::MAIN_WORLD,
1589        );
1590        assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK)));
1591        assert!(matches!(
1592            image.get_color_at(0, 10),
1593            Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 })
1594        ));
1595        assert!(matches!(
1596            image.get_color_at(5, 10),
1597            Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 })
1598        ));
1599    }
1600}