1#[cfg(feature = "basis-universal")]
2use super::basis::*;
3#[cfg(feature = "dds")]
4use super::dds::*;
5#[cfg(feature = "ktx2")]
6use super::ktx2::*;
7#[cfg(not(feature = "bevy_reflect"))]
8use bevy_reflect::TypePath;
9#[cfg(feature = "bevy_reflect")]
10use bevy_reflect::{std_traits::ReflectDefault, Reflect};
11
12use bevy_asset::{Asset, RenderAssetUsages};
13use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza};
14use bevy_math::{AspectRatio, UVec2, UVec3, Vec2};
15use core::hash::Hash;
16use serde::{Deserialize, Serialize};
17use thiserror::Error;
18use tracing::warn;
19use wgpu_types::{
20 AddressMode, CompareFunction, Extent3d, Features, FilterMode, SamplerBorderColor,
21 SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
22 TextureViewDescriptor,
23};
24
25pub trait BevyDefault {
26 fn bevy_default() -> Self;
27}
28
29impl BevyDefault for TextureFormat {
30 fn bevy_default() -> Self {
31 TextureFormat::Rgba8UnormSrgb
32 }
33}
34
35pub const TEXTURE_ASSET_INDEX: u64 = 0;
36pub const SAMPLER_ASSET_INDEX: u64 = 1;
37
38#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
39pub enum ImageFormat {
40 #[cfg(feature = "basis-universal")]
41 Basis,
42 #[cfg(feature = "bmp")]
43 Bmp,
44 #[cfg(feature = "dds")]
45 Dds,
46 #[cfg(feature = "ff")]
47 Farbfeld,
48 #[cfg(feature = "gif")]
49 Gif,
50 #[cfg(feature = "exr")]
51 OpenExr,
52 #[cfg(feature = "hdr")]
53 Hdr,
54 #[cfg(feature = "ico")]
55 Ico,
56 #[cfg(feature = "jpeg")]
57 Jpeg,
58 #[cfg(feature = "ktx2")]
59 Ktx2,
60 #[cfg(feature = "png")]
61 Png,
62 #[cfg(feature = "pnm")]
63 Pnm,
64 #[cfg(feature = "qoi")]
65 Qoi,
66 #[cfg(feature = "tga")]
67 Tga,
68 #[cfg(feature = "tiff")]
69 Tiff,
70 #[cfg(feature = "webp")]
71 WebP,
72}
73
74macro_rules! feature_gate {
75 ($feature: tt, $value: ident) => {{
76 #[cfg(not(feature = $feature))]
77 {
78 tracing::warn!("feature \"{}\" is not enabled", $feature);
79 return None;
80 }
81 #[cfg(feature = $feature)]
82 ImageFormat::$value
83 }};
84}
85
86impl ImageFormat {
87 pub const fn to_file_extensions(&self) -> &'static [&'static str] {
89 match self {
90 #[cfg(feature = "basis-universal")]
91 ImageFormat::Basis => &["basis"],
92 #[cfg(feature = "bmp")]
93 ImageFormat::Bmp => &["bmp"],
94 #[cfg(feature = "dds")]
95 ImageFormat::Dds => &["dds"],
96 #[cfg(feature = "ff")]
97 ImageFormat::Farbfeld => &["ff", "farbfeld"],
98 #[cfg(feature = "gif")]
99 ImageFormat::Gif => &["gif"],
100 #[cfg(feature = "exr")]
101 ImageFormat::OpenExr => &["exr"],
102 #[cfg(feature = "hdr")]
103 ImageFormat::Hdr => &["hdr"],
104 #[cfg(feature = "ico")]
105 ImageFormat::Ico => &["ico"],
106 #[cfg(feature = "jpeg")]
107 ImageFormat::Jpeg => &["jpg", "jpeg"],
108 #[cfg(feature = "ktx2")]
109 ImageFormat::Ktx2 => &["ktx2"],
110 #[cfg(feature = "pnm")]
111 ImageFormat::Pnm => &["pam", "pbm", "pgm", "ppm"],
112 #[cfg(feature = "png")]
113 ImageFormat::Png => &["png"],
114 #[cfg(feature = "qoi")]
115 ImageFormat::Qoi => &["qoi"],
116 #[cfg(feature = "tga")]
117 ImageFormat::Tga => &["tga"],
118 #[cfg(feature = "tiff")]
119 ImageFormat::Tiff => &["tif", "tiff"],
120 #[cfg(feature = "webp")]
121 ImageFormat::WebP => &["webp"],
122 #[expect(
124 clippy::allow_attributes,
125 reason = "`unreachable_patterns` may not always lint"
126 )]
127 #[allow(
128 unreachable_patterns,
129 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
130 )]
131 _ => &[],
132 }
133 }
134
135 pub const fn to_mime_types(&self) -> &'static [&'static str] {
139 match self {
140 #[cfg(feature = "basis-universal")]
141 ImageFormat::Basis => &["image/basis", "image/x-basis"],
142 #[cfg(feature = "bmp")]
143 ImageFormat::Bmp => &["image/bmp", "image/x-bmp"],
144 #[cfg(feature = "dds")]
145 ImageFormat::Dds => &["image/vnd-ms.dds"],
146 #[cfg(feature = "hdr")]
147 ImageFormat::Hdr => &["image/vnd.radiance"],
148 #[cfg(feature = "gif")]
149 ImageFormat::Gif => &["image/gif"],
150 #[cfg(feature = "ff")]
151 ImageFormat::Farbfeld => &[],
152 #[cfg(feature = "ico")]
153 ImageFormat::Ico => &["image/x-icon"],
154 #[cfg(feature = "jpeg")]
155 ImageFormat::Jpeg => &["image/jpeg"],
156 #[cfg(feature = "ktx2")]
157 ImageFormat::Ktx2 => &["image/ktx2"],
158 #[cfg(feature = "png")]
159 ImageFormat::Png => &["image/png"],
160 #[cfg(feature = "qoi")]
161 ImageFormat::Qoi => &["image/qoi", "image/x-qoi"],
162 #[cfg(feature = "exr")]
163 ImageFormat::OpenExr => &["image/x-exr"],
164 #[cfg(feature = "pnm")]
165 ImageFormat::Pnm => &[
166 "image/x-portable-bitmap",
167 "image/x-portable-graymap",
168 "image/x-portable-pixmap",
169 "image/x-portable-anymap",
170 ],
171 #[cfg(feature = "tga")]
172 ImageFormat::Tga => &["image/x-targa", "image/x-tga"],
173 #[cfg(feature = "tiff")]
174 ImageFormat::Tiff => &["image/tiff"],
175 #[cfg(feature = "webp")]
176 ImageFormat::WebP => &["image/webp"],
177 #[expect(
179 clippy::allow_attributes,
180 reason = "`unreachable_patterns` may not always lint"
181 )]
182 #[allow(
183 unreachable_patterns,
184 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
185 )]
186 _ => &[],
187 }
188 }
189
190 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
191 #[expect(
192 clippy::allow_attributes,
193 reason = "`unreachable_code` may not always lint"
194 )]
195 #[allow(
196 unreachable_code,
197 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
198 )]
199 Some(match mime_type.to_ascii_lowercase().as_str() {
200 "image/basis" | "image/x-basis" => feature_gate!("basis-universal", Basis),
202 "image/bmp" | "image/x-bmp" => feature_gate!("bmp", Bmp),
203 "image/vnd-ms.dds" => feature_gate!("dds", Dds),
204 "image/vnd.radiance" => feature_gate!("hdr", Hdr),
205 "image/gif" => feature_gate!("gif", Gif),
206 "image/x-icon" => feature_gate!("ico", Ico),
207 "image/jpeg" => feature_gate!("jpeg", Jpeg),
208 "image/ktx2" => feature_gate!("ktx2", Ktx2),
209 "image/png" => feature_gate!("png", Png),
210 "image/qoi" | "image/x-qoi" => feature_gate!("qoi", Qoi),
211 "image/x-exr" => feature_gate!("exr", OpenExr),
212 "image/x-portable-bitmap"
213 | "image/x-portable-graymap"
214 | "image/x-portable-pixmap"
215 | "image/x-portable-anymap" => feature_gate!("pnm", Pnm),
216 "image/x-targa" | "image/x-tga" => feature_gate!("tga", Tga),
217 "image/tiff" => feature_gate!("tiff", Tiff),
218 "image/webp" => feature_gate!("webp", WebP),
219 _ => return None,
220 })
221 }
222
223 pub fn from_extension(extension: &str) -> Option<Self> {
224 #[expect(
225 clippy::allow_attributes,
226 reason = "`unreachable_code` may not always lint"
227 )]
228 #[allow(
229 unreachable_code,
230 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
231 )]
232 Some(match extension.to_ascii_lowercase().as_str() {
233 "basis" => feature_gate!("basis-universal", Basis),
234 "bmp" => feature_gate!("bmp", Bmp),
235 "dds" => feature_gate!("dds", Dds),
236 "ff" | "farbfeld" => feature_gate!("ff", Farbfeld),
237 "gif" => feature_gate!("gif", Gif),
238 "exr" => feature_gate!("exr", OpenExr),
239 "hdr" => feature_gate!("hdr", Hdr),
240 "ico" => feature_gate!("ico", Ico),
241 "jpg" | "jpeg" => feature_gate!("jpeg", Jpeg),
242 "ktx2" => feature_gate!("ktx2", Ktx2),
243 "pam" | "pbm" | "pgm" | "ppm" => feature_gate!("pnm", Pnm),
244 "png" => feature_gate!("png", Png),
245 "qoi" => feature_gate!("qoi", Qoi),
246 "tga" => feature_gate!("tga", Tga),
247 "tif" | "tiff" => feature_gate!("tiff", Tiff),
248 "webp" => feature_gate!("webp", WebP),
249 _ => return None,
250 })
251 }
252
253 pub fn as_image_crate_format(&self) -> Option<image::ImageFormat> {
254 #[expect(
255 clippy::allow_attributes,
256 reason = "`unreachable_code` may not always lint"
257 )]
258 #[allow(
259 unreachable_code,
260 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
261 )]
262 Some(match self {
263 #[cfg(feature = "bmp")]
264 ImageFormat::Bmp => image::ImageFormat::Bmp,
265 #[cfg(feature = "dds")]
266 ImageFormat::Dds => image::ImageFormat::Dds,
267 #[cfg(feature = "ff")]
268 ImageFormat::Farbfeld => image::ImageFormat::Farbfeld,
269 #[cfg(feature = "gif")]
270 ImageFormat::Gif => image::ImageFormat::Gif,
271 #[cfg(feature = "exr")]
272 ImageFormat::OpenExr => image::ImageFormat::OpenExr,
273 #[cfg(feature = "hdr")]
274 ImageFormat::Hdr => image::ImageFormat::Hdr,
275 #[cfg(feature = "ico")]
276 ImageFormat::Ico => image::ImageFormat::Ico,
277 #[cfg(feature = "jpeg")]
278 ImageFormat::Jpeg => image::ImageFormat::Jpeg,
279 #[cfg(feature = "png")]
280 ImageFormat::Png => image::ImageFormat::Png,
281 #[cfg(feature = "pnm")]
282 ImageFormat::Pnm => image::ImageFormat::Pnm,
283 #[cfg(feature = "qoi")]
284 ImageFormat::Qoi => image::ImageFormat::Qoi,
285 #[cfg(feature = "tga")]
286 ImageFormat::Tga => image::ImageFormat::Tga,
287 #[cfg(feature = "tiff")]
288 ImageFormat::Tiff => image::ImageFormat::Tiff,
289 #[cfg(feature = "webp")]
290 ImageFormat::WebP => image::ImageFormat::WebP,
291 #[cfg(feature = "basis-universal")]
292 ImageFormat::Basis => return None,
293 #[cfg(feature = "ktx2")]
294 ImageFormat::Ktx2 => return None,
295 #[expect(
297 clippy::allow_attributes,
298 reason = "`unreachable_patterns` may not always lint"
299 )]
300 #[allow(
301 unreachable_patterns,
302 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
303 )]
304 _ => return None,
305 })
306 }
307
308 pub fn from_image_crate_format(format: image::ImageFormat) -> Option<ImageFormat> {
309 #[expect(
310 clippy::allow_attributes,
311 reason = "`unreachable_code` may not always lint"
312 )]
313 #[allow(
314 unreachable_code,
315 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
316 )]
317 Some(match format {
318 image::ImageFormat::Bmp => feature_gate!("bmp", Bmp),
319 image::ImageFormat::Dds => feature_gate!("dds", Dds),
320 image::ImageFormat::Farbfeld => feature_gate!("ff", Farbfeld),
321 image::ImageFormat::Gif => feature_gate!("gif", Gif),
322 image::ImageFormat::OpenExr => feature_gate!("exr", OpenExr),
323 image::ImageFormat::Hdr => feature_gate!("hdr", Hdr),
324 image::ImageFormat::Ico => feature_gate!("ico", Ico),
325 image::ImageFormat::Jpeg => feature_gate!("jpeg", Jpeg),
326 image::ImageFormat::Png => feature_gate!("png", Png),
327 image::ImageFormat::Pnm => feature_gate!("pnm", Pnm),
328 image::ImageFormat::Qoi => feature_gate!("qoi", Qoi),
329 image::ImageFormat::Tga => feature_gate!("tga", Tga),
330 image::ImageFormat::Tiff => feature_gate!("tiff", Tiff),
331 image::ImageFormat::WebP => feature_gate!("webp", WebP),
332 _ => return None,
333 })
334 }
335}
336
337#[derive(Asset, Debug, Clone)]
338#[cfg_attr(
339 feature = "bevy_reflect",
340 derive(Reflect),
341 reflect(opaque, Default, Debug, Clone)
342)]
343#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))]
344pub struct Image {
345 pub data: Option<Vec<u8>>,
350 pub texture_descriptor: TextureDescriptor<Option<&'static str>, &'static [TextureFormat]>,
352 pub sampler: ImageSampler,
354 pub texture_view_descriptor: Option<TextureViewDescriptor<Option<&'static str>>>,
355 pub asset_usage: RenderAssetUsages,
356}
357
358#[derive(Debug, Default, Clone, Serialize, Deserialize)]
362pub enum ImageSampler {
363 #[default]
365 Default,
366 Descriptor(ImageSamplerDescriptor),
368}
369
370impl ImageSampler {
371 #[inline]
373 pub fn linear() -> ImageSampler {
374 ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
375 }
376
377 #[inline]
379 pub fn nearest() -> ImageSampler {
380 ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
381 }
382
383 pub fn get_or_init_descriptor(&mut self) -> &mut ImageSamplerDescriptor {
388 match self {
389 ImageSampler::Default => {
390 *self = ImageSampler::Descriptor(ImageSamplerDescriptor::default());
391 match self {
392 ImageSampler::Descriptor(descriptor) => descriptor,
393 _ => unreachable!(),
394 }
395 }
396 ImageSampler::Descriptor(descriptor) => descriptor,
397 }
398 }
399}
400
401#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
407pub enum ImageAddressMode {
408 #[default]
413 ClampToEdge,
414 Repeat,
419 MirrorRepeat,
424 ClampToBorder,
430}
431
432#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
436pub enum ImageFilterMode {
437 #[default]
441 Nearest,
442 Linear,
446}
447
448#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
452pub enum ImageCompareFunction {
453 Never,
455 Less,
457 Equal,
461 LessEqual,
463 Greater,
465 NotEqual,
469 GreaterEqual,
471 Always,
473}
474
475#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
479pub enum ImageSamplerBorderColor {
480 TransparentBlack,
482 OpaqueBlack,
484 OpaqueWhite,
486 Zero,
492}
493
494#[derive(Clone, Debug, Serialize, Deserialize)]
502pub struct ImageSamplerDescriptor {
503 pub label: Option<String>,
504 pub address_mode_u: ImageAddressMode,
506 pub address_mode_v: ImageAddressMode,
508 pub address_mode_w: ImageAddressMode,
510 pub mag_filter: ImageFilterMode,
512 pub min_filter: ImageFilterMode,
514 pub mipmap_filter: ImageFilterMode,
516 pub lod_min_clamp: f32,
518 pub lod_max_clamp: f32,
520 pub compare: Option<ImageCompareFunction>,
522 pub anisotropy_clamp: u16,
524 pub border_color: Option<ImageSamplerBorderColor>,
526}
527
528impl Default for ImageSamplerDescriptor {
529 fn default() -> Self {
530 Self {
531 address_mode_u: Default::default(),
532 address_mode_v: Default::default(),
533 address_mode_w: Default::default(),
534 mag_filter: Default::default(),
535 min_filter: Default::default(),
536 mipmap_filter: Default::default(),
537 lod_min_clamp: 0.0,
538 lod_max_clamp: 32.0,
539 compare: None,
540 anisotropy_clamp: 1,
541 border_color: None,
542 label: None,
543 }
544 }
545}
546
547impl ImageSamplerDescriptor {
548 #[inline]
550 pub fn linear() -> ImageSamplerDescriptor {
551 ImageSamplerDescriptor {
552 mag_filter: ImageFilterMode::Linear,
553 min_filter: ImageFilterMode::Linear,
554 mipmap_filter: ImageFilterMode::Linear,
555 ..Default::default()
556 }
557 }
558
559 #[inline]
561 pub fn nearest() -> ImageSamplerDescriptor {
562 ImageSamplerDescriptor {
563 mag_filter: ImageFilterMode::Nearest,
564 min_filter: ImageFilterMode::Nearest,
565 mipmap_filter: ImageFilterMode::Nearest,
566 ..Default::default()
567 }
568 }
569
570 pub fn as_wgpu(&self) -> SamplerDescriptor<Option<&str>> {
571 SamplerDescriptor {
572 label: self.label.as_deref(),
573 address_mode_u: self.address_mode_u.into(),
574 address_mode_v: self.address_mode_v.into(),
575 address_mode_w: self.address_mode_w.into(),
576 mag_filter: self.mag_filter.into(),
577 min_filter: self.min_filter.into(),
578 mipmap_filter: self.mipmap_filter.into(),
579 lod_min_clamp: self.lod_min_clamp,
580 lod_max_clamp: self.lod_max_clamp,
581 compare: self.compare.map(Into::into),
582 anisotropy_clamp: self.anisotropy_clamp,
583 border_color: self.border_color.map(Into::into),
584 }
585 }
586}
587
588impl From<ImageAddressMode> for AddressMode {
589 fn from(value: ImageAddressMode) -> Self {
590 match value {
591 ImageAddressMode::ClampToEdge => AddressMode::ClampToEdge,
592 ImageAddressMode::Repeat => AddressMode::Repeat,
593 ImageAddressMode::MirrorRepeat => AddressMode::MirrorRepeat,
594 ImageAddressMode::ClampToBorder => AddressMode::ClampToBorder,
595 }
596 }
597}
598
599impl From<ImageFilterMode> for FilterMode {
600 fn from(value: ImageFilterMode) -> Self {
601 match value {
602 ImageFilterMode::Nearest => FilterMode::Nearest,
603 ImageFilterMode::Linear => FilterMode::Linear,
604 }
605 }
606}
607
608impl From<ImageCompareFunction> for CompareFunction {
609 fn from(value: ImageCompareFunction) -> Self {
610 match value {
611 ImageCompareFunction::Never => CompareFunction::Never,
612 ImageCompareFunction::Less => CompareFunction::Less,
613 ImageCompareFunction::Equal => CompareFunction::Equal,
614 ImageCompareFunction::LessEqual => CompareFunction::LessEqual,
615 ImageCompareFunction::Greater => CompareFunction::Greater,
616 ImageCompareFunction::NotEqual => CompareFunction::NotEqual,
617 ImageCompareFunction::GreaterEqual => CompareFunction::GreaterEqual,
618 ImageCompareFunction::Always => CompareFunction::Always,
619 }
620 }
621}
622
623impl From<ImageSamplerBorderColor> for SamplerBorderColor {
624 fn from(value: ImageSamplerBorderColor) -> Self {
625 match value {
626 ImageSamplerBorderColor::TransparentBlack => SamplerBorderColor::TransparentBlack,
627 ImageSamplerBorderColor::OpaqueBlack => SamplerBorderColor::OpaqueBlack,
628 ImageSamplerBorderColor::OpaqueWhite => SamplerBorderColor::OpaqueWhite,
629 ImageSamplerBorderColor::Zero => SamplerBorderColor::Zero,
630 }
631 }
632}
633
634impl From<AddressMode> for ImageAddressMode {
635 fn from(value: AddressMode) -> Self {
636 match value {
637 AddressMode::ClampToEdge => ImageAddressMode::ClampToEdge,
638 AddressMode::Repeat => ImageAddressMode::Repeat,
639 AddressMode::MirrorRepeat => ImageAddressMode::MirrorRepeat,
640 AddressMode::ClampToBorder => ImageAddressMode::ClampToBorder,
641 }
642 }
643}
644
645impl From<FilterMode> for ImageFilterMode {
646 fn from(value: FilterMode) -> Self {
647 match value {
648 FilterMode::Nearest => ImageFilterMode::Nearest,
649 FilterMode::Linear => ImageFilterMode::Linear,
650 }
651 }
652}
653
654impl From<CompareFunction> for ImageCompareFunction {
655 fn from(value: CompareFunction) -> Self {
656 match value {
657 CompareFunction::Never => ImageCompareFunction::Never,
658 CompareFunction::Less => ImageCompareFunction::Less,
659 CompareFunction::Equal => ImageCompareFunction::Equal,
660 CompareFunction::LessEqual => ImageCompareFunction::LessEqual,
661 CompareFunction::Greater => ImageCompareFunction::Greater,
662 CompareFunction::NotEqual => ImageCompareFunction::NotEqual,
663 CompareFunction::GreaterEqual => ImageCompareFunction::GreaterEqual,
664 CompareFunction::Always => ImageCompareFunction::Always,
665 }
666 }
667}
668
669impl From<SamplerBorderColor> for ImageSamplerBorderColor {
670 fn from(value: SamplerBorderColor) -> Self {
671 match value {
672 SamplerBorderColor::TransparentBlack => ImageSamplerBorderColor::TransparentBlack,
673 SamplerBorderColor::OpaqueBlack => ImageSamplerBorderColor::OpaqueBlack,
674 SamplerBorderColor::OpaqueWhite => ImageSamplerBorderColor::OpaqueWhite,
675 SamplerBorderColor::Zero => ImageSamplerBorderColor::Zero,
676 }
677 }
678}
679
680impl From<SamplerDescriptor<Option<&str>>> for ImageSamplerDescriptor {
681 fn from(value: SamplerDescriptor<Option<&str>>) -> Self {
682 ImageSamplerDescriptor {
683 label: value.label.map(ToString::to_string),
684 address_mode_u: value.address_mode_u.into(),
685 address_mode_v: value.address_mode_v.into(),
686 address_mode_w: value.address_mode_w.into(),
687 mag_filter: value.mag_filter.into(),
688 min_filter: value.min_filter.into(),
689 mipmap_filter: value.mipmap_filter.into(),
690 lod_min_clamp: value.lod_min_clamp,
691 lod_max_clamp: value.lod_max_clamp,
692 compare: value.compare.map(Into::into),
693 anisotropy_clamp: value.anisotropy_clamp,
694 border_color: value.border_color.map(Into::into),
695 }
696 }
697}
698
699impl Default for Image {
700 fn default() -> Self {
702 let mut image = Image::default_uninit();
703 image.data = Some(vec![255; image.texture_descriptor.format.pixel_size()]);
704 image
705 }
706}
707
708impl Image {
709 pub fn new(
715 size: Extent3d,
716 dimension: TextureDimension,
717 data: Vec<u8>,
718 format: TextureFormat,
719 asset_usage: RenderAssetUsages,
720 ) -> Self {
721 debug_assert_eq!(
722 size.volume() * format.pixel_size(),
723 data.len(),
724 "Pixel data, size and format have to match",
725 );
726 let mut image = Image::new_uninit(size, dimension, format, asset_usage);
727 image.data = Some(data);
728 image
729 }
730
731 pub fn new_uninit(
733 size: Extent3d,
734 dimension: TextureDimension,
735 format: TextureFormat,
736 asset_usage: RenderAssetUsages,
737 ) -> Self {
738 Image {
739 data: None,
740 texture_descriptor: TextureDescriptor {
741 size,
742 format,
743 dimension,
744 label: None,
745 mip_level_count: 1,
746 sample_count: 1,
747 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
748 view_formats: &[],
749 },
750 sampler: ImageSampler::Default,
751 texture_view_descriptor: None,
752 asset_usage,
753 }
754 }
755
756 pub fn transparent() -> Image {
760 let format = TextureFormat::bevy_default();
764 debug_assert!(format.pixel_size() == 4);
765 let data = vec![255, 255, 255, 0];
766 Image::new(
767 Extent3d {
768 width: 1,
769 height: 1,
770 depth_or_array_layers: 1,
771 },
772 TextureDimension::D2,
773 data,
774 format,
775 RenderAssetUsages::default(),
776 )
777 }
778 pub fn default_uninit() -> Image {
780 Image::new_uninit(
781 Extent3d {
782 width: 1,
783 height: 1,
784 depth_or_array_layers: 1,
785 },
786 TextureDimension::D2,
787 TextureFormat::bevy_default(),
788 RenderAssetUsages::default(),
789 )
790 }
791
792 pub fn new_fill(
798 size: Extent3d,
799 dimension: TextureDimension,
800 pixel: &[u8],
801 format: TextureFormat,
802 asset_usage: RenderAssetUsages,
803 ) -> Self {
804 let byte_len = format.pixel_size() * size.volume();
805 debug_assert_eq!(
806 pixel.len() % format.pixel_size(),
807 0,
808 "Must not have incomplete pixel data (pixel size is {}B).",
809 format.pixel_size(),
810 );
811 debug_assert!(
812 pixel.len() <= byte_len,
813 "Fill data must fit within pixel buffer (expected {}B).",
814 byte_len,
815 );
816 let data = pixel.iter().copied().cycle().take(byte_len).collect();
817 Image::new(size, dimension, data, format, asset_usage)
818 }
819
820 #[inline]
822 pub fn width(&self) -> u32 {
823 self.texture_descriptor.size.width
824 }
825
826 #[inline]
828 pub fn height(&self) -> u32 {
829 self.texture_descriptor.size.height
830 }
831
832 #[inline]
834 pub fn aspect_ratio(&self) -> AspectRatio {
835 AspectRatio::try_from_pixels(self.width(), self.height()).expect(
836 "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
837 )
838 }
839
840 #[inline]
842 pub fn size_f32(&self) -> Vec2 {
843 Vec2::new(self.width() as f32, self.height() as f32)
844 }
845
846 #[inline]
848 pub fn size(&self) -> UVec2 {
849 UVec2::new(self.width(), self.height())
850 }
851
852 pub fn resize(&mut self, size: Extent3d) {
855 self.texture_descriptor.size = size;
856 if let Some(ref mut data) = self.data {
857 data.resize(
858 size.volume() * self.texture_descriptor.format.pixel_size(),
859 0,
860 );
861 } else {
862 warn!("Resized an uninitialized image. Directly modify image.texture_descriptor.size instead");
863 }
864 }
865
866 pub fn reinterpret_size(&mut self, new_size: Extent3d) {
872 assert_eq!(
873 new_size.volume(),
874 self.texture_descriptor.size.volume(),
875 "Incompatible sizes: old = {:?} new = {:?}",
876 self.texture_descriptor.size,
877 new_size
878 );
879
880 self.texture_descriptor.size = new_size;
881 }
882
883 pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) {
891 assert_eq!(self.texture_descriptor.dimension, TextureDimension::D2);
893 assert_eq!(self.texture_descriptor.size.depth_or_array_layers, 1);
894 assert_eq!(self.height() % layers, 0);
895
896 self.reinterpret_size(Extent3d {
897 width: self.width(),
898 height: self.height() / layers,
899 depth_or_array_layers: layers,
900 });
901 }
902
903 pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
912 self.clone()
913 .try_into_dynamic()
914 .ok()
915 .and_then(|img| match new_format {
916 TextureFormat::R8Unorm => {
917 Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
918 }
919 TextureFormat::Rg8Unorm => Some((
920 image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
921 false,
922 )),
923 TextureFormat::Rgba8UnormSrgb => {
924 Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
925 }
926 _ => None,
927 })
928 .map(|(dyn_img, is_srgb)| Self::from_dynamic(dyn_img, is_srgb, self.asset_usage))
929 }
930
931 pub fn from_buffer(
934 #[cfg(all(debug_assertions, feature = "dds"))] name: String,
935 buffer: &[u8],
936 image_type: ImageType,
937 #[cfg_attr(
938 not(any(feature = "basis-universal", feature = "dds", feature = "ktx2")),
939 expect(unused_variables, reason = "only used with certain features")
940 )]
941 supported_compressed_formats: CompressedImageFormats,
942 is_srgb: bool,
943 image_sampler: ImageSampler,
944 asset_usage: RenderAssetUsages,
945 ) -> Result<Image, TextureError> {
946 let format = image_type.to_image_format()?;
947
948 let mut image = match format {
955 #[cfg(feature = "basis-universal")]
956 ImageFormat::Basis => {
957 basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
958 }
959 #[cfg(feature = "dds")]
960 ImageFormat::Dds => dds_buffer_to_image(
961 #[cfg(debug_assertions)]
962 name,
963 buffer,
964 supported_compressed_formats,
965 is_srgb,
966 )?,
967 #[cfg(feature = "ktx2")]
968 ImageFormat::Ktx2 => {
969 ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
970 }
971 #[expect(
972 clippy::allow_attributes,
973 reason = "`unreachable_patterns` may not always lint"
974 )]
975 #[allow(
976 unreachable_patterns,
977 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"
978 )]
979 _ => {
980 let image_crate_format = format
981 .as_image_crate_format()
982 .ok_or_else(|| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?;
983 let mut reader = image::ImageReader::new(std::io::Cursor::new(buffer));
984 reader.set_format(image_crate_format);
985 reader.no_limits();
986 let dyn_img = reader.decode()?;
987 Self::from_dynamic(dyn_img, is_srgb, asset_usage)
988 }
989 };
990 image.sampler = image_sampler;
991 Ok(image)
992 }
993
994 pub fn is_compressed(&self) -> bool {
996 let format_description = self.texture_descriptor.format;
997 format_description
998 .required_features()
999 .contains(Features::TEXTURE_COMPRESSION_ASTC)
1000 || format_description
1001 .required_features()
1002 .contains(Features::TEXTURE_COMPRESSION_BC)
1003 || format_description
1004 .required_features()
1005 .contains(Features::TEXTURE_COMPRESSION_ETC2)
1006 }
1007
1008 #[inline(always)]
1014 pub fn pixel_data_offset(&self, coords: UVec3) -> Option<usize> {
1015 let width = self.texture_descriptor.size.width;
1016 let height = self.texture_descriptor.size.height;
1017 let depth = self.texture_descriptor.size.depth_or_array_layers;
1018
1019 let pixel_size = self.texture_descriptor.format.pixel_size();
1020 let pixel_offset = match self.texture_descriptor.dimension {
1021 TextureDimension::D3 | TextureDimension::D2 => {
1022 if coords.x >= width || coords.y >= height || coords.z >= depth {
1023 return None;
1024 }
1025 coords.z * height * width + coords.y * width + coords.x
1026 }
1027 TextureDimension::D1 => {
1028 if coords.x >= width {
1029 return None;
1030 }
1031 coords.x
1032 }
1033 };
1034
1035 Some(pixel_offset as usize * pixel_size)
1036 }
1037
1038 #[inline(always)]
1040 pub fn pixel_bytes(&self, coords: UVec3) -> Option<&[u8]> {
1041 let len = self.texture_descriptor.format.pixel_size();
1042 let data = self.data.as_ref()?;
1043 self.pixel_data_offset(coords)
1044 .map(|start| &data[start..(start + len)])
1045 }
1046
1047 #[inline(always)]
1049 pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Option<&mut [u8]> {
1050 let len = self.texture_descriptor.format.pixel_size();
1051 let offset = self.pixel_data_offset(coords);
1052 let data = self.data.as_mut()?;
1053 offset.map(|start| &mut data[start..(start + len)])
1054 }
1055
1056 #[inline(always)]
1060 pub fn get_color_at_1d(&self, x: u32) -> Result<Color, TextureAccessError> {
1061 if self.texture_descriptor.dimension != TextureDimension::D1 {
1062 return Err(TextureAccessError::WrongDimension);
1063 }
1064 self.get_color_at_internal(UVec3::new(x, 0, 0))
1065 }
1066
1067 #[inline(always)]
1091 pub fn get_color_at(&self, x: u32, y: u32) -> Result<Color, TextureAccessError> {
1092 if self.texture_descriptor.dimension != TextureDimension::D2 {
1093 return Err(TextureAccessError::WrongDimension);
1094 }
1095 self.get_color_at_internal(UVec3::new(x, y, 0))
1096 }
1097
1098 #[inline(always)]
1102 pub fn get_color_at_3d(&self, x: u32, y: u32, z: u32) -> Result<Color, TextureAccessError> {
1103 match (
1104 self.texture_descriptor.dimension,
1105 self.texture_descriptor.size.depth_or_array_layers,
1106 ) {
1107 (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1108 self.get_color_at_internal(UVec3::new(x, y, z))
1109 }
1110 _ => Err(TextureAccessError::WrongDimension),
1111 }
1112 }
1113
1114 #[inline(always)]
1118 pub fn set_color_at_1d(&mut self, x: u32, color: Color) -> Result<(), TextureAccessError> {
1119 if self.texture_descriptor.dimension != TextureDimension::D1 {
1120 return Err(TextureAccessError::WrongDimension);
1121 }
1122 self.set_color_at_internal(UVec3::new(x, 0, 0), color)
1123 }
1124
1125 #[inline(always)]
1147 pub fn set_color_at(&mut self, x: u32, y: u32, color: Color) -> Result<(), TextureAccessError> {
1148 if self.texture_descriptor.dimension != TextureDimension::D2 {
1149 return Err(TextureAccessError::WrongDimension);
1150 }
1151 self.set_color_at_internal(UVec3::new(x, y, 0), color)
1152 }
1153
1154 #[inline(always)]
1158 pub fn set_color_at_3d(
1159 &mut self,
1160 x: u32,
1161 y: u32,
1162 z: u32,
1163 color: Color,
1164 ) -> Result<(), TextureAccessError> {
1165 match (
1166 self.texture_descriptor.dimension,
1167 self.texture_descriptor.size.depth_or_array_layers,
1168 ) {
1169 (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1170 self.set_color_at_internal(UVec3::new(x, y, z), color)
1171 }
1172 _ => Err(TextureAccessError::WrongDimension),
1173 }
1174 }
1175
1176 #[inline(always)]
1177 fn get_color_at_internal(&self, coords: UVec3) -> Result<Color, TextureAccessError> {
1178 let Some(bytes) = self.pixel_bytes(coords) else {
1179 return Err(TextureAccessError::OutOfBounds {
1180 x: coords.x,
1181 y: coords.y,
1182 z: coords.z,
1183 });
1184 };
1185
1186 match self.texture_descriptor.format {
1189 TextureFormat::Rgba8UnormSrgb => Ok(Color::srgba(
1190 bytes[0] as f32 / u8::MAX as f32,
1191 bytes[1] as f32 / u8::MAX as f32,
1192 bytes[2] as f32 / u8::MAX as f32,
1193 bytes[3] as f32 / u8::MAX as f32,
1194 )),
1195 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => Ok(Color::linear_rgba(
1196 bytes[0] as f32 / u8::MAX as f32,
1197 bytes[1] as f32 / u8::MAX as f32,
1198 bytes[2] as f32 / u8::MAX as f32,
1199 bytes[3] as f32 / u8::MAX as f32,
1200 )),
1201 TextureFormat::Bgra8UnormSrgb => Ok(Color::srgba(
1202 bytes[2] as f32 / u8::MAX as f32,
1203 bytes[1] as f32 / u8::MAX as f32,
1204 bytes[0] as f32 / u8::MAX as f32,
1205 bytes[3] as f32 / u8::MAX as f32,
1206 )),
1207 TextureFormat::Bgra8Unorm => Ok(Color::linear_rgba(
1208 bytes[2] as f32 / u8::MAX as f32,
1209 bytes[1] as f32 / u8::MAX as f32,
1210 bytes[0] as f32 / u8::MAX as f32,
1211 bytes[3] as f32 / u8::MAX as f32,
1212 )),
1213 TextureFormat::Rgba32Float => Ok(Color::linear_rgba(
1214 f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1215 f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1216 f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1217 f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1218 )),
1219 TextureFormat::Rgba16Float => Ok(Color::linear_rgba(
1220 half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32(),
1221 half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32(),
1222 half::f16::from_le_bytes([bytes[4], bytes[5]]).to_f32(),
1223 half::f16::from_le_bytes([bytes[6], bytes[7]]).to_f32(),
1224 )),
1225 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1226 let (r, g, b, a) = (
1227 u16::from_le_bytes([bytes[0], bytes[1]]),
1228 u16::from_le_bytes([bytes[2], bytes[3]]),
1229 u16::from_le_bytes([bytes[4], bytes[5]]),
1230 u16::from_le_bytes([bytes[6], bytes[7]]),
1231 );
1232 Ok(Color::linear_rgba(
1233 (r as f64 / u16::MAX as f64) as f32,
1235 (g as f64 / u16::MAX as f64) as f32,
1236 (b as f64 / u16::MAX as f64) as f32,
1237 (a as f64 / u16::MAX as f64) as f32,
1238 ))
1239 }
1240 TextureFormat::Rgba32Uint => {
1241 let (r, g, b, a) = (
1242 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1243 u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1244 u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1245 u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1246 );
1247 Ok(Color::linear_rgba(
1248 (r as f64 / u32::MAX as f64) as f32,
1250 (g as f64 / u32::MAX as f64) as f32,
1251 (b as f64 / u32::MAX as f64) as f32,
1252 (a as f64 / u32::MAX as f64) as f32,
1253 ))
1254 }
1255 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1258 let x = bytes[0] as f32 / u8::MAX as f32;
1259 Ok(Color::linear_rgb(x, x, x))
1260 }
1261 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1262 let x = u16::from_le_bytes([bytes[0], bytes[1]]);
1263 let x = (x as f64 / u16::MAX as f64) as f32;
1265 Ok(Color::linear_rgb(x, x, x))
1266 }
1267 TextureFormat::R32Uint => {
1268 let x = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1269 let x = (x as f64 / u32::MAX as f64) as f32;
1271 Ok(Color::linear_rgb(x, x, x))
1272 }
1273 TextureFormat::R16Float => {
1274 let x = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1275 Ok(Color::linear_rgb(x, x, x))
1276 }
1277 TextureFormat::R32Float => {
1278 let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1279 Ok(Color::linear_rgb(x, x, x))
1280 }
1281 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1282 let r = bytes[0] as f32 / u8::MAX as f32;
1283 let g = bytes[1] as f32 / u8::MAX as f32;
1284 Ok(Color::linear_rgb(r, g, 0.0))
1285 }
1286 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1287 let r = u16::from_le_bytes([bytes[0], bytes[1]]);
1288 let g = u16::from_le_bytes([bytes[2], bytes[3]]);
1289 let r = (r as f64 / u16::MAX as f64) as f32;
1291 let g = (g as f64 / u16::MAX as f64) as f32;
1292 Ok(Color::linear_rgb(r, g, 0.0))
1293 }
1294 TextureFormat::Rg32Uint => {
1295 let r = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1296 let g = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1297 let r = (r as f64 / u32::MAX as f64) as f32;
1299 let g = (g as f64 / u32::MAX as f64) as f32;
1300 Ok(Color::linear_rgb(r, g, 0.0))
1301 }
1302 TextureFormat::Rg16Float => {
1303 let r = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1304 let g = half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32();
1305 Ok(Color::linear_rgb(r, g, 0.0))
1306 }
1307 TextureFormat::Rg32Float => {
1308 let r = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1309 let g = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1310 Ok(Color::linear_rgb(r, g, 0.0))
1311 }
1312 _ => Err(TextureAccessError::UnsupportedTextureFormat(
1313 self.texture_descriptor.format,
1314 )),
1315 }
1316 }
1317
1318 #[inline(always)]
1319 fn set_color_at_internal(
1320 &mut self,
1321 coords: UVec3,
1322 color: Color,
1323 ) -> Result<(), TextureAccessError> {
1324 let format = self.texture_descriptor.format;
1325
1326 let Some(bytes) = self.pixel_bytes_mut(coords) else {
1327 return Err(TextureAccessError::OutOfBounds {
1328 x: coords.x,
1329 y: coords.y,
1330 z: coords.z,
1331 });
1332 };
1333
1334 match format {
1337 TextureFormat::Rgba8UnormSrgb => {
1338 let [r, g, b, a] = Srgba::from(color).to_f32_array();
1339 bytes[0] = (r * u8::MAX as f32) as u8;
1340 bytes[1] = (g * u8::MAX as f32) as u8;
1341 bytes[2] = (b * u8::MAX as f32) as u8;
1342 bytes[3] = (a * u8::MAX as f32) as u8;
1343 }
1344 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => {
1345 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1346 bytes[0] = (r * u8::MAX as f32) as u8;
1347 bytes[1] = (g * u8::MAX as f32) as u8;
1348 bytes[2] = (b * u8::MAX as f32) as u8;
1349 bytes[3] = (a * u8::MAX as f32) as u8;
1350 }
1351 TextureFormat::Bgra8UnormSrgb => {
1352 let [r, g, b, a] = Srgba::from(color).to_f32_array();
1353 bytes[0] = (b * u8::MAX as f32) as u8;
1354 bytes[1] = (g * u8::MAX as f32) as u8;
1355 bytes[2] = (r * u8::MAX as f32) as u8;
1356 bytes[3] = (a * u8::MAX as f32) as u8;
1357 }
1358 TextureFormat::Bgra8Unorm => {
1359 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1360 bytes[0] = (b * u8::MAX as f32) as u8;
1361 bytes[1] = (g * u8::MAX as f32) as u8;
1362 bytes[2] = (r * u8::MAX as f32) as u8;
1363 bytes[3] = (a * u8::MAX as f32) as u8;
1364 }
1365 TextureFormat::Rgba16Float => {
1366 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1367 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1368 bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1369 bytes[4..6].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(b)));
1370 bytes[6..8].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(a)));
1371 }
1372 TextureFormat::Rgba32Float => {
1373 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1374 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1375 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1376 bytes[8..12].copy_from_slice(&f32::to_le_bytes(b));
1377 bytes[12..16].copy_from_slice(&f32::to_le_bytes(a));
1378 }
1379 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1380 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1381 let [r, g, b, a] = [
1382 (r * u16::MAX as f32) as u16,
1383 (g * u16::MAX as f32) as u16,
1384 (b * u16::MAX as f32) as u16,
1385 (a * u16::MAX as f32) as u16,
1386 ];
1387 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1388 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1389 bytes[4..6].copy_from_slice(&u16::to_le_bytes(b));
1390 bytes[6..8].copy_from_slice(&u16::to_le_bytes(a));
1391 }
1392 TextureFormat::Rgba32Uint => {
1393 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1394 let [r, g, b, a] = [
1395 (r * u32::MAX as f32) as u32,
1396 (g * u32::MAX as f32) as u32,
1397 (b * u32::MAX as f32) as u32,
1398 (a * u32::MAX as f32) as u32,
1399 ];
1400 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1401 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1402 bytes[8..12].copy_from_slice(&u32::to_le_bytes(b));
1403 bytes[12..16].copy_from_slice(&u32::to_le_bytes(a));
1404 }
1405 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1406 let linear = LinearRgba::from(color);
1408 let luminance = Xyza::from(linear).y;
1409 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1410 bytes[0] = (r * u8::MAX as f32) as u8;
1411 }
1412 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1413 let linear = LinearRgba::from(color);
1415 let luminance = Xyza::from(linear).y;
1416 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1417 let r = (r * u16::MAX as f32) as u16;
1418 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1419 }
1420 TextureFormat::R32Uint => {
1421 let linear = LinearRgba::from(color);
1423 let luminance = Xyza::from(linear).y;
1424 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1425 let r = (r as f64 * u32::MAX as f64) as u32;
1427 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1428 }
1429 TextureFormat::R16Float => {
1430 let linear = LinearRgba::from(color);
1432 let luminance = Xyza::from(linear).y;
1433 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1434 let x = half::f16::from_f32(r);
1435 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(x));
1436 }
1437 TextureFormat::R32Float => {
1438 let linear = LinearRgba::from(color);
1440 let luminance = Xyza::from(linear).y;
1441 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1442 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1443 }
1444 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1445 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1446 bytes[0] = (r * u8::MAX as f32) as u8;
1447 bytes[1] = (g * u8::MAX as f32) as u8;
1448 }
1449 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1450 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1451 let r = (r * u16::MAX as f32) as u16;
1452 let g = (g * u16::MAX as f32) as u16;
1453 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1454 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1455 }
1456 TextureFormat::Rg32Uint => {
1457 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1458 let r = (r as f64 * u32::MAX as f64) as u32;
1460 let g = (g as f64 * u32::MAX as f64) as u32;
1461 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1462 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1463 }
1464 TextureFormat::Rg16Float => {
1465 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1466 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1467 bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1468 }
1469 TextureFormat::Rg32Float => {
1470 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1471 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1472 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1473 }
1474 _ => {
1475 return Err(TextureAccessError::UnsupportedTextureFormat(
1476 self.texture_descriptor.format,
1477 ));
1478 }
1479 }
1480 Ok(())
1481 }
1482}
1483
1484#[derive(Clone, Copy, Debug)]
1485pub enum DataFormat {
1486 Rgb,
1487 Rgba,
1488 Rrr,
1489 Rrrg,
1490 Rg,
1491}
1492
1493#[derive(Clone, Copy, Debug)]
1495pub enum TranscodeFormat {
1496 Etc1s,
1497 Uastc(DataFormat),
1498 R8UnormSrgb,
1500 Rg8UnormSrgb,
1502 Rgb8,
1504}
1505
1506#[derive(Error, Debug)]
1508pub enum TextureAccessError {
1509 #[error("out of bounds (x: {x}, y: {y}, z: {z})")]
1510 OutOfBounds { x: u32, y: u32, z: u32 },
1511 #[error("unsupported texture format: {0:?}")]
1512 UnsupportedTextureFormat(TextureFormat),
1513 #[error("attempt to access texture with different dimension")]
1514 WrongDimension,
1515}
1516
1517#[derive(Error, Debug)]
1519pub enum TextureError {
1520 #[error("invalid image mime type: {0}")]
1522 InvalidImageMimeType(String),
1523 #[error("invalid image extension: {0}")]
1525 InvalidImageExtension(String),
1526 #[error("failed to load an image: {0}")]
1528 ImageError(#[from] image::ImageError),
1529 #[error("unsupported texture format: {0}")]
1531 UnsupportedTextureFormat(String),
1532 #[error("supercompression not supported: {0}")]
1534 SuperCompressionNotSupported(String),
1535 #[error("failed to decompress an image: {0}")]
1537 SuperDecompressionError(String),
1538 #[error("invalid data: {0}")]
1540 InvalidData(String),
1541 #[error("transcode error: {0}")]
1543 TranscodeError(String),
1544 #[error("format requires transcoding: {0:?}")]
1546 FormatRequiresTranscodingError(TranscodeFormat),
1547 #[error("only cubemaps with six faces are supported")]
1549 IncompleteCubemap,
1550}
1551
1552#[derive(Debug)]
1554pub enum ImageType<'a> {
1555 MimeType(&'a str),
1557 Extension(&'a str),
1559 Format(ImageFormat),
1561}
1562
1563impl<'a> ImageType<'a> {
1564 pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
1565 match self {
1566 ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
1567 .ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
1568 ImageType::Extension(extension) => ImageFormat::from_extension(extension)
1569 .ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
1570 ImageType::Format(format) => Ok(*format),
1571 }
1572 }
1573}
1574
1575pub trait Volume {
1577 fn volume(&self) -> usize;
1578}
1579
1580impl Volume for Extent3d {
1581 fn volume(&self) -> usize {
1583 (self.width * self.height * self.depth_or_array_layers) as usize
1584 }
1585}
1586
1587pub trait TextureFormatPixelInfo {
1589 fn pixel_size(&self) -> usize;
1591}
1592
1593impl TextureFormatPixelInfo for TextureFormat {
1594 fn pixel_size(&self) -> usize {
1595 let info = self;
1596 match info.block_dimensions() {
1597 (1, 1) => info.block_copy_size(None).unwrap() as usize,
1598 _ => panic!("Using pixel_size for compressed textures is invalid"),
1599 }
1600 }
1601}
1602
1603bitflags::bitflags! {
1604 #[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
1605 #[repr(transparent)]
1606 pub struct CompressedImageFormats: u32 {
1607 const NONE = 0;
1608 const ASTC_LDR = 1 << 0;
1609 const BC = 1 << 1;
1610 const ETC2 = 1 << 2;
1611 }
1612}
1613
1614impl CompressedImageFormats {
1615 pub fn from_features(features: Features) -> Self {
1616 let mut supported_compressed_formats = Self::default();
1617 if features.contains(Features::TEXTURE_COMPRESSION_ASTC) {
1618 supported_compressed_formats |= Self::ASTC_LDR;
1619 }
1620 if features.contains(Features::TEXTURE_COMPRESSION_BC) {
1621 supported_compressed_formats |= Self::BC;
1622 }
1623 if features.contains(Features::TEXTURE_COMPRESSION_ETC2) {
1624 supported_compressed_formats |= Self::ETC2;
1625 }
1626 supported_compressed_formats
1627 }
1628
1629 pub fn supports(&self, format: TextureFormat) -> bool {
1630 match format {
1631 TextureFormat::Bc1RgbaUnorm
1632 | TextureFormat::Bc1RgbaUnormSrgb
1633 | TextureFormat::Bc2RgbaUnorm
1634 | TextureFormat::Bc2RgbaUnormSrgb
1635 | TextureFormat::Bc3RgbaUnorm
1636 | TextureFormat::Bc3RgbaUnormSrgb
1637 | TextureFormat::Bc4RUnorm
1638 | TextureFormat::Bc4RSnorm
1639 | TextureFormat::Bc5RgUnorm
1640 | TextureFormat::Bc5RgSnorm
1641 | TextureFormat::Bc6hRgbUfloat
1642 | TextureFormat::Bc6hRgbFloat
1643 | TextureFormat::Bc7RgbaUnorm
1644 | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
1645 TextureFormat::Etc2Rgb8Unorm
1646 | TextureFormat::Etc2Rgb8UnormSrgb
1647 | TextureFormat::Etc2Rgb8A1Unorm
1648 | TextureFormat::Etc2Rgb8A1UnormSrgb
1649 | TextureFormat::Etc2Rgba8Unorm
1650 | TextureFormat::Etc2Rgba8UnormSrgb
1651 | TextureFormat::EacR11Unorm
1652 | TextureFormat::EacR11Snorm
1653 | TextureFormat::EacRg11Unorm
1654 | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
1655 TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
1656 _ => true,
1657 }
1658 }
1659}
1660
1661#[cfg(test)]
1662mod test {
1663 use super::*;
1664
1665 #[test]
1666 fn image_size() {
1667 let size = Extent3d {
1668 width: 200,
1669 height: 100,
1670 depth_or_array_layers: 1,
1671 };
1672 let image = Image::new_fill(
1673 size,
1674 TextureDimension::D2,
1675 &[0, 0, 0, 255],
1676 TextureFormat::Rgba8Unorm,
1677 RenderAssetUsages::MAIN_WORLD,
1678 );
1679 assert_eq!(
1680 Vec2::new(size.width as f32, size.height as f32),
1681 image.size_f32()
1682 );
1683 }
1684
1685 #[test]
1686 fn image_default_size() {
1687 let image = Image::default();
1688 assert_eq!(UVec2::ONE, image.size());
1689 assert_eq!(Vec2::ONE, image.size_f32());
1690 }
1691
1692 #[test]
1693 fn on_edge_pixel_is_invalid() {
1694 let image = Image::new_fill(
1695 Extent3d {
1696 width: 5,
1697 height: 10,
1698 depth_or_array_layers: 1,
1699 },
1700 TextureDimension::D2,
1701 &[0, 0, 0, 255],
1702 TextureFormat::Rgba8Unorm,
1703 RenderAssetUsages::MAIN_WORLD,
1704 );
1705 assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK)));
1706 assert!(matches!(
1707 image.get_color_at(0, 10),
1708 Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 })
1709 ));
1710 assert!(matches!(
1711 image.get_color_at(5, 10),
1712 Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 })
1713 ));
1714 }
1715
1716 #[test]
1717 fn get_set_pixel_2d_with_layers() {
1718 let mut image = Image::new_fill(
1719 Extent3d {
1720 width: 5,
1721 height: 10,
1722 depth_or_array_layers: 3,
1723 },
1724 TextureDimension::D2,
1725 &[0, 0, 0, 255],
1726 TextureFormat::Rgba8Unorm,
1727 RenderAssetUsages::MAIN_WORLD,
1728 );
1729 image.set_color_at_3d(0, 0, 0, Color::WHITE).unwrap();
1730 assert!(matches!(image.get_color_at_3d(0, 0, 0), Ok(Color::WHITE)));
1731 image.set_color_at_3d(2, 3, 1, Color::WHITE).unwrap();
1732 assert!(matches!(image.get_color_at_3d(2, 3, 1), Ok(Color::WHITE)));
1733 image.set_color_at_3d(4, 9, 2, Color::WHITE).unwrap();
1734 assert!(matches!(image.get_color_at_3d(4, 9, 2), Ok(Color::WHITE)));
1735 }
1736}