1use crate::ImageLoader;
2
3#[cfg(feature = "basis-universal")]
4use super::basis::*;
5#[cfg(feature = "dds")]
6use super::dds::*;
7#[cfg(feature = "ktx2")]
8use super::ktx2::*;
9use bevy_app::{App, Plugin};
10#[cfg(not(feature = "bevy_reflect"))]
11use bevy_reflect::TypePath;
12#[cfg(feature = "bevy_reflect")]
13use bevy_reflect::{std_traits::ReflectDefault, Reflect};
14
15use bevy_asset::{uuid_handle, Asset, AssetApp, Assets, Handle, RenderAssetUsages};
16use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza};
17use bevy_ecs::resource::Resource;
18use bevy_math::{AspectRatio, UVec2, UVec3, Vec2};
19use core::hash::Hash;
20use serde::{Deserialize, Serialize};
21use thiserror::Error;
22use wgpu_types::{
23 AddressMode, CompareFunction, Extent3d, Features, FilterMode, SamplerBorderColor,
24 SamplerDescriptor, TextureDataOrder, TextureDescriptor, TextureDimension, TextureFormat,
25 TextureUsages, TextureViewDescriptor,
26};
27
28pub trait BevyDefault {
31 fn bevy_default() -> Self;
33}
34
35impl BevyDefault for TextureFormat {
36 fn bevy_default() -> Self {
37 TextureFormat::Rgba8UnormSrgb
38 }
39}
40
41pub trait TextureSrgbViewFormats {
43 fn srgb_view_formats(&self) -> &'static [TextureFormat];
45}
46
47impl TextureSrgbViewFormats for TextureFormat {
48 fn srgb_view_formats(&self) -> &'static [TextureFormat] {
52 match self {
53 TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
54 TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb],
55 TextureFormat::Bc1RgbaUnorm => &[TextureFormat::Bc1RgbaUnormSrgb],
56 TextureFormat::Bc2RgbaUnorm => &[TextureFormat::Bc2RgbaUnormSrgb],
57 TextureFormat::Bc3RgbaUnorm => &[TextureFormat::Bc3RgbaUnormSrgb],
58 TextureFormat::Bc7RgbaUnorm => &[TextureFormat::Bc7RgbaUnormSrgb],
59 TextureFormat::Etc2Rgb8Unorm => &[TextureFormat::Etc2Rgb8UnormSrgb],
60 TextureFormat::Etc2Rgb8A1Unorm => &[TextureFormat::Etc2Rgb8A1UnormSrgb],
61 TextureFormat::Etc2Rgba8Unorm => &[TextureFormat::Etc2Rgba8UnormSrgb],
62 TextureFormat::Astc {
63 block: wgpu_types::AstcBlock::B4x4,
64 channel: wgpu_types::AstcChannel::Unorm,
65 } => &[TextureFormat::Astc {
66 block: wgpu_types::AstcBlock::B4x4,
67 channel: wgpu_types::AstcChannel::UnormSrgb,
68 }],
69 TextureFormat::Astc {
70 block: wgpu_types::AstcBlock::B5x4,
71 channel: wgpu_types::AstcChannel::Unorm,
72 } => &[TextureFormat::Astc {
73 block: wgpu_types::AstcBlock::B5x4,
74 channel: wgpu_types::AstcChannel::UnormSrgb,
75 }],
76 TextureFormat::Astc {
77 block: wgpu_types::AstcBlock::B5x5,
78 channel: wgpu_types::AstcChannel::Unorm,
79 } => &[TextureFormat::Astc {
80 block: wgpu_types::AstcBlock::B5x5,
81 channel: wgpu_types::AstcChannel::UnormSrgb,
82 }],
83 TextureFormat::Astc {
84 block: wgpu_types::AstcBlock::B6x5,
85 channel: wgpu_types::AstcChannel::Unorm,
86 } => &[TextureFormat::Astc {
87 block: wgpu_types::AstcBlock::B6x5,
88 channel: wgpu_types::AstcChannel::UnormSrgb,
89 }],
90 TextureFormat::Astc {
91 block: wgpu_types::AstcBlock::B6x6,
92 channel: wgpu_types::AstcChannel::Unorm,
93 } => &[TextureFormat::Astc {
94 block: wgpu_types::AstcBlock::B6x6,
95 channel: wgpu_types::AstcChannel::UnormSrgb,
96 }],
97 TextureFormat::Astc {
98 block: wgpu_types::AstcBlock::B8x5,
99 channel: wgpu_types::AstcChannel::Unorm,
100 } => &[TextureFormat::Astc {
101 block: wgpu_types::AstcBlock::B8x5,
102 channel: wgpu_types::AstcChannel::UnormSrgb,
103 }],
104 TextureFormat::Astc {
105 block: wgpu_types::AstcBlock::B8x6,
106 channel: wgpu_types::AstcChannel::Unorm,
107 } => &[TextureFormat::Astc {
108 block: wgpu_types::AstcBlock::B8x6,
109 channel: wgpu_types::AstcChannel::UnormSrgb,
110 }],
111 TextureFormat::Astc {
112 block: wgpu_types::AstcBlock::B8x8,
113 channel: wgpu_types::AstcChannel::Unorm,
114 } => &[TextureFormat::Astc {
115 block: wgpu_types::AstcBlock::B8x8,
116 channel: wgpu_types::AstcChannel::UnormSrgb,
117 }],
118 TextureFormat::Astc {
119 block: wgpu_types::AstcBlock::B10x5,
120 channel: wgpu_types::AstcChannel::Unorm,
121 } => &[TextureFormat::Astc {
122 block: wgpu_types::AstcBlock::B10x5,
123 channel: wgpu_types::AstcChannel::UnormSrgb,
124 }],
125 TextureFormat::Astc {
126 block: wgpu_types::AstcBlock::B10x6,
127 channel: wgpu_types::AstcChannel::Unorm,
128 } => &[TextureFormat::Astc {
129 block: wgpu_types::AstcBlock::B10x6,
130 channel: wgpu_types::AstcChannel::UnormSrgb,
131 }],
132 TextureFormat::Astc {
133 block: wgpu_types::AstcBlock::B10x8,
134 channel: wgpu_types::AstcChannel::Unorm,
135 } => &[TextureFormat::Astc {
136 block: wgpu_types::AstcBlock::B10x8,
137 channel: wgpu_types::AstcChannel::UnormSrgb,
138 }],
139 TextureFormat::Astc {
140 block: wgpu_types::AstcBlock::B10x10,
141 channel: wgpu_types::AstcChannel::Unorm,
142 } => &[TextureFormat::Astc {
143 block: wgpu_types::AstcBlock::B10x10,
144 channel: wgpu_types::AstcChannel::UnormSrgb,
145 }],
146 TextureFormat::Astc {
147 block: wgpu_types::AstcBlock::B12x10,
148 channel: wgpu_types::AstcChannel::Unorm,
149 } => &[TextureFormat::Astc {
150 block: wgpu_types::AstcBlock::B12x10,
151 channel: wgpu_types::AstcChannel::UnormSrgb,
152 }],
153 TextureFormat::Astc {
154 block: wgpu_types::AstcBlock::B12x12,
155 channel: wgpu_types::AstcChannel::Unorm,
156 } => &[TextureFormat::Astc {
157 block: wgpu_types::AstcBlock::B12x12,
158 channel: wgpu_types::AstcChannel::UnormSrgb,
159 }],
160 _ => &[],
161 }
162 }
163}
164
165pub const TRANSPARENT_IMAGE_HANDLE: Handle<Image> =
171 uuid_handle!("d18ad97e-a322-4981-9505-44c59a4b5e46");
172
173pub struct ImagePlugin {
175 pub default_sampler: ImageSamplerDescriptor,
177}
178
179impl Default for ImagePlugin {
180 fn default() -> Self {
181 ImagePlugin::default_linear()
182 }
183}
184
185impl ImagePlugin {
186 pub fn default_linear() -> ImagePlugin {
188 ImagePlugin {
189 default_sampler: ImageSamplerDescriptor::linear(),
190 }
191 }
192
193 pub fn default_nearest() -> ImagePlugin {
195 ImagePlugin {
196 default_sampler: ImageSamplerDescriptor::nearest(),
197 }
198 }
199}
200
201impl Plugin for ImagePlugin {
202 fn build(&self, app: &mut App) {
203 #[cfg(feature = "exr")]
204 app.init_asset_loader::<crate::ExrTextureLoader>();
205
206 #[cfg(feature = "hdr")]
207 app.init_asset_loader::<crate::HdrTextureLoader>();
208
209 app.init_asset::<Image>();
210 #[cfg(feature = "bevy_reflect")]
211 app.register_asset_reflect::<Image>();
212
213 let mut image_assets = app.world_mut().resource_mut::<Assets<Image>>();
214
215 image_assets
216 .insert(&Handle::default(), Image::default())
217 .unwrap();
218 image_assets
219 .insert(&TRANSPARENT_IMAGE_HANDLE, Image::transparent())
220 .unwrap();
221
222 #[cfg(feature = "compressed_image_saver")]
223 if let Some(processor) = app
224 .world()
225 .get_resource::<bevy_asset::processor::AssetProcessor>()
226 {
227 processor.register_processor::<bevy_asset::processor::LoadTransformAndSave<
228 ImageLoader,
229 bevy_asset::transformer::IdentityAssetTransformer<Image>,
230 crate::CompressedImageSaver,
231 >>(crate::CompressedImageSaver.into());
232 processor.set_default_processor::<bevy_asset::processor::LoadTransformAndSave<
233 ImageLoader,
234 bevy_asset::transformer::IdentityAssetTransformer<Image>,
235 crate::CompressedImageSaver,
236 >>("png");
237 }
238
239 app.preregister_asset_loader::<ImageLoader>(ImageLoader::SUPPORTED_FILE_EXTENSIONS);
240 }
241}
242
243pub const TEXTURE_ASSET_INDEX: u64 = 0;
244pub const SAMPLER_ASSET_INDEX: u64 = 1;
245
246#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
247pub enum ImageFormat {
248 #[cfg(feature = "basis-universal")]
249 Basis,
250 #[cfg(feature = "bmp")]
251 Bmp,
252 #[cfg(feature = "dds")]
253 Dds,
254 #[cfg(feature = "ff")]
255 Farbfeld,
256 #[cfg(feature = "gif")]
257 Gif,
258 #[cfg(feature = "exr")]
259 OpenExr,
260 #[cfg(feature = "hdr")]
261 Hdr,
262 #[cfg(feature = "ico")]
263 Ico,
264 #[cfg(feature = "jpeg")]
265 Jpeg,
266 #[cfg(feature = "ktx2")]
267 Ktx2,
268 #[cfg(feature = "png")]
269 Png,
270 #[cfg(feature = "pnm")]
271 Pnm,
272 #[cfg(feature = "qoi")]
273 Qoi,
274 #[cfg(feature = "tga")]
275 Tga,
276 #[cfg(feature = "tiff")]
277 Tiff,
278 #[cfg(feature = "webp")]
279 WebP,
280}
281
282macro_rules! feature_gate {
283 ($feature: tt, $value: ident) => {{
284 #[cfg(not(feature = $feature))]
285 {
286 tracing::warn!("feature \"{}\" is not enabled", $feature);
287 return None;
288 }
289 #[cfg(feature = $feature)]
290 ImageFormat::$value
291 }};
292}
293
294impl ImageFormat {
295 pub const fn to_file_extensions(&self) -> &'static [&'static str] {
297 match self {
298 #[cfg(feature = "basis-universal")]
299 ImageFormat::Basis => &["basis"],
300 #[cfg(feature = "bmp")]
301 ImageFormat::Bmp => &["bmp"],
302 #[cfg(feature = "dds")]
303 ImageFormat::Dds => &["dds"],
304 #[cfg(feature = "ff")]
305 ImageFormat::Farbfeld => &["ff", "farbfeld"],
306 #[cfg(feature = "gif")]
307 ImageFormat::Gif => &["gif"],
308 #[cfg(feature = "exr")]
309 ImageFormat::OpenExr => &["exr"],
310 #[cfg(feature = "hdr")]
311 ImageFormat::Hdr => &["hdr"],
312 #[cfg(feature = "ico")]
313 ImageFormat::Ico => &["ico"],
314 #[cfg(feature = "jpeg")]
315 ImageFormat::Jpeg => &["jpg", "jpeg"],
316 #[cfg(feature = "ktx2")]
317 ImageFormat::Ktx2 => &["ktx2"],
318 #[cfg(feature = "pnm")]
319 ImageFormat::Pnm => &["pam", "pbm", "pgm", "ppm"],
320 #[cfg(feature = "png")]
321 ImageFormat::Png => &["png"],
322 #[cfg(feature = "qoi")]
323 ImageFormat::Qoi => &["qoi"],
324 #[cfg(feature = "tga")]
325 ImageFormat::Tga => &["tga"],
326 #[cfg(feature = "tiff")]
327 ImageFormat::Tiff => &["tif", "tiff"],
328 #[cfg(feature = "webp")]
329 ImageFormat::WebP => &["webp"],
330 #[expect(
332 clippy::allow_attributes,
333 reason = "`unreachable_patterns` may not always lint"
334 )]
335 #[allow(
336 unreachable_patterns,
337 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
338 )]
339 _ => &[],
340 }
341 }
342
343 pub const fn to_mime_types(&self) -> &'static [&'static str] {
347 match self {
348 #[cfg(feature = "basis-universal")]
349 ImageFormat::Basis => &["image/basis", "image/x-basis"],
350 #[cfg(feature = "bmp")]
351 ImageFormat::Bmp => &["image/bmp", "image/x-bmp"],
352 #[cfg(feature = "dds")]
353 ImageFormat::Dds => &["image/vnd-ms.dds"],
354 #[cfg(feature = "hdr")]
355 ImageFormat::Hdr => &["image/vnd.radiance"],
356 #[cfg(feature = "gif")]
357 ImageFormat::Gif => &["image/gif"],
358 #[cfg(feature = "ff")]
359 ImageFormat::Farbfeld => &[],
360 #[cfg(feature = "ico")]
361 ImageFormat::Ico => &["image/x-icon"],
362 #[cfg(feature = "jpeg")]
363 ImageFormat::Jpeg => &["image/jpeg"],
364 #[cfg(feature = "ktx2")]
365 ImageFormat::Ktx2 => &["image/ktx2"],
366 #[cfg(feature = "png")]
367 ImageFormat::Png => &["image/png"],
368 #[cfg(feature = "qoi")]
369 ImageFormat::Qoi => &["image/qoi", "image/x-qoi"],
370 #[cfg(feature = "exr")]
371 ImageFormat::OpenExr => &["image/x-exr"],
372 #[cfg(feature = "pnm")]
373 ImageFormat::Pnm => &[
374 "image/x-portable-bitmap",
375 "image/x-portable-graymap",
376 "image/x-portable-pixmap",
377 "image/x-portable-anymap",
378 ],
379 #[cfg(feature = "tga")]
380 ImageFormat::Tga => &["image/x-targa", "image/x-tga"],
381 #[cfg(feature = "tiff")]
382 ImageFormat::Tiff => &["image/tiff"],
383 #[cfg(feature = "webp")]
384 ImageFormat::WebP => &["image/webp"],
385 #[expect(
387 clippy::allow_attributes,
388 reason = "`unreachable_patterns` may not always lint"
389 )]
390 #[allow(
391 unreachable_patterns,
392 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
393 )]
394 _ => &[],
395 }
396 }
397
398 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
399 #[expect(
400 clippy::allow_attributes,
401 reason = "`unreachable_code` may not always lint"
402 )]
403 #[allow(
404 unreachable_code,
405 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
406 )]
407 Some(match mime_type.to_ascii_lowercase().as_str() {
408 "image/basis" | "image/x-basis" => feature_gate!("basis-universal", Basis),
410 "image/bmp" | "image/x-bmp" => feature_gate!("bmp", Bmp),
411 "image/vnd-ms.dds" => feature_gate!("dds", Dds),
412 "image/vnd.radiance" => feature_gate!("hdr", Hdr),
413 "image/gif" => feature_gate!("gif", Gif),
414 "image/x-icon" => feature_gate!("ico", Ico),
415 "image/jpeg" => feature_gate!("jpeg", Jpeg),
416 "image/ktx2" => feature_gate!("ktx2", Ktx2),
417 "image/png" => feature_gate!("png", Png),
418 "image/qoi" | "image/x-qoi" => feature_gate!("qoi", Qoi),
419 "image/x-exr" => feature_gate!("exr", OpenExr),
420 "image/x-portable-bitmap"
421 | "image/x-portable-graymap"
422 | "image/x-portable-pixmap"
423 | "image/x-portable-anymap" => feature_gate!("pnm", Pnm),
424 "image/x-targa" | "image/x-tga" => feature_gate!("tga", Tga),
425 "image/tiff" => feature_gate!("tiff", Tiff),
426 "image/webp" => feature_gate!("webp", WebP),
427 _ => return None,
428 })
429 }
430
431 pub fn from_extension(extension: &str) -> Option<Self> {
432 #[expect(
433 clippy::allow_attributes,
434 reason = "`unreachable_code` may not always lint"
435 )]
436 #[allow(
437 unreachable_code,
438 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
439 )]
440 Some(match extension.to_ascii_lowercase().as_str() {
441 "basis" => feature_gate!("basis-universal", Basis),
442 "bmp" => feature_gate!("bmp", Bmp),
443 "dds" => feature_gate!("dds", Dds),
444 "ff" | "farbfeld" => feature_gate!("ff", Farbfeld),
445 "gif" => feature_gate!("gif", Gif),
446 "exr" => feature_gate!("exr", OpenExr),
447 "hdr" => feature_gate!("hdr", Hdr),
448 "ico" => feature_gate!("ico", Ico),
449 "jpg" | "jpeg" => feature_gate!("jpeg", Jpeg),
450 "ktx2" => feature_gate!("ktx2", Ktx2),
451 "pam" | "pbm" | "pgm" | "ppm" => feature_gate!("pnm", Pnm),
452 "png" => feature_gate!("png", Png),
453 "qoi" => feature_gate!("qoi", Qoi),
454 "tga" => feature_gate!("tga", Tga),
455 "tif" | "tiff" => feature_gate!("tiff", Tiff),
456 "webp" => feature_gate!("webp", WebP),
457 _ => return None,
458 })
459 }
460
461 pub fn as_image_crate_format(&self) -> Option<image::ImageFormat> {
462 #[expect(
463 clippy::allow_attributes,
464 reason = "`unreachable_code` may not always lint"
465 )]
466 #[allow(
467 unreachable_code,
468 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
469 )]
470 Some(match self {
471 #[cfg(feature = "bmp")]
472 ImageFormat::Bmp => image::ImageFormat::Bmp,
473 #[cfg(feature = "dds")]
474 ImageFormat::Dds => image::ImageFormat::Dds,
475 #[cfg(feature = "ff")]
476 ImageFormat::Farbfeld => image::ImageFormat::Farbfeld,
477 #[cfg(feature = "gif")]
478 ImageFormat::Gif => image::ImageFormat::Gif,
479 #[cfg(feature = "exr")]
480 ImageFormat::OpenExr => image::ImageFormat::OpenExr,
481 #[cfg(feature = "hdr")]
482 ImageFormat::Hdr => image::ImageFormat::Hdr,
483 #[cfg(feature = "ico")]
484 ImageFormat::Ico => image::ImageFormat::Ico,
485 #[cfg(feature = "jpeg")]
486 ImageFormat::Jpeg => image::ImageFormat::Jpeg,
487 #[cfg(feature = "png")]
488 ImageFormat::Png => image::ImageFormat::Png,
489 #[cfg(feature = "pnm")]
490 ImageFormat::Pnm => image::ImageFormat::Pnm,
491 #[cfg(feature = "qoi")]
492 ImageFormat::Qoi => image::ImageFormat::Qoi,
493 #[cfg(feature = "tga")]
494 ImageFormat::Tga => image::ImageFormat::Tga,
495 #[cfg(feature = "tiff")]
496 ImageFormat::Tiff => image::ImageFormat::Tiff,
497 #[cfg(feature = "webp")]
498 ImageFormat::WebP => image::ImageFormat::WebP,
499 #[cfg(feature = "basis-universal")]
500 ImageFormat::Basis => return None,
501 #[cfg(feature = "ktx2")]
502 ImageFormat::Ktx2 => return None,
503 #[expect(
505 clippy::allow_attributes,
506 reason = "`unreachable_patterns` may not always lint"
507 )]
508 #[allow(
509 unreachable_patterns,
510 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
511 )]
512 _ => return None,
513 })
514 }
515
516 pub fn from_image_crate_format(format: image::ImageFormat) -> Option<ImageFormat> {
517 #[expect(
518 clippy::allow_attributes,
519 reason = "`unreachable_code` may not always lint"
520 )]
521 #[allow(
522 unreachable_code,
523 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
524 )]
525 Some(match format {
526 image::ImageFormat::Bmp => feature_gate!("bmp", Bmp),
527 image::ImageFormat::Dds => feature_gate!("dds", Dds),
528 image::ImageFormat::Farbfeld => feature_gate!("ff", Farbfeld),
529 image::ImageFormat::Gif => feature_gate!("gif", Gif),
530 image::ImageFormat::OpenExr => feature_gate!("exr", OpenExr),
531 image::ImageFormat::Hdr => feature_gate!("hdr", Hdr),
532 image::ImageFormat::Ico => feature_gate!("ico", Ico),
533 image::ImageFormat::Jpeg => feature_gate!("jpeg", Jpeg),
534 image::ImageFormat::Png => feature_gate!("png", Png),
535 image::ImageFormat::Pnm => feature_gate!("pnm", Pnm),
536 image::ImageFormat::Qoi => feature_gate!("qoi", Qoi),
537 image::ImageFormat::Tga => feature_gate!("tga", Tga),
538 image::ImageFormat::Tiff => feature_gate!("tiff", Tiff),
539 image::ImageFormat::WebP => feature_gate!("webp", WebP),
540 _ => return None,
541 })
542 }
543}
544
545pub trait ToExtents {
546 fn to_extents(self) -> Extent3d;
547}
548impl ToExtents for UVec2 {
549 fn to_extents(self) -> Extent3d {
550 Extent3d {
551 width: self.x,
552 height: self.y,
553 depth_or_array_layers: 1,
554 }
555 }
556}
557impl ToExtents for UVec3 {
558 fn to_extents(self) -> Extent3d {
559 Extent3d {
560 width: self.x,
561 height: self.y,
562 depth_or_array_layers: self.z,
563 }
564 }
565}
566
567#[derive(Asset, Debug, Clone, PartialEq)]
574#[cfg_attr(
575 feature = "bevy_reflect",
576 derive(Reflect),
577 reflect(opaque, Default, Debug, Clone)
578)]
579#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))]
580pub struct Image {
581 pub data: Option<Vec<u8>>,
586 pub data_order: TextureDataOrder,
590 pub texture_descriptor: TextureDescriptor<Option<&'static str>, &'static [TextureFormat]>,
599 pub sampler: ImageSampler,
601 pub texture_view_descriptor: Option<TextureViewDescriptor<Option<&'static str>>>,
609 pub asset_usage: RenderAssetUsages,
610 pub copy_on_resize: bool,
612}
613
614#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
618pub enum ImageSampler {
619 #[default]
621 Default,
622 Descriptor(ImageSamplerDescriptor),
624}
625
626impl ImageSampler {
627 #[inline]
629 pub fn linear() -> ImageSampler {
630 ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
631 }
632
633 #[inline]
635 pub fn nearest() -> ImageSampler {
636 ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
637 }
638
639 pub fn get_or_init_descriptor(&mut self) -> &mut ImageSamplerDescriptor {
644 match self {
645 ImageSampler::Default => {
646 *self = ImageSampler::Descriptor(ImageSamplerDescriptor::default());
647 match self {
648 ImageSampler::Descriptor(descriptor) => descriptor,
649 _ => unreachable!(),
650 }
651 }
652 ImageSampler::Descriptor(descriptor) => descriptor,
653 }
654 }
655}
656
657#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
663pub enum ImageAddressMode {
664 #[default]
669 ClampToEdge,
670 Repeat,
675 MirrorRepeat,
680 ClampToBorder,
686}
687
688#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
692pub enum ImageFilterMode {
693 #[default]
697 Nearest,
698 Linear,
702}
703
704#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
708pub enum ImageCompareFunction {
709 Never,
711 Less,
713 Equal,
717 LessEqual,
719 Greater,
721 NotEqual,
725 GreaterEqual,
727 Always,
729}
730
731#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
735pub enum ImageSamplerBorderColor {
736 TransparentBlack,
738 OpaqueBlack,
740 OpaqueWhite,
742 Zero,
748}
749
750#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
758pub struct ImageSamplerDescriptor {
759 pub label: Option<String>,
760 pub address_mode_u: ImageAddressMode,
762 pub address_mode_v: ImageAddressMode,
764 pub address_mode_w: ImageAddressMode,
766 pub mag_filter: ImageFilterMode,
768 pub min_filter: ImageFilterMode,
770 pub mipmap_filter: ImageFilterMode,
772 pub lod_min_clamp: f32,
774 pub lod_max_clamp: f32,
776 pub compare: Option<ImageCompareFunction>,
778 pub anisotropy_clamp: u16,
780 pub border_color: Option<ImageSamplerBorderColor>,
782}
783
784impl Default for ImageSamplerDescriptor {
785 fn default() -> Self {
786 Self {
787 address_mode_u: Default::default(),
788 address_mode_v: Default::default(),
789 address_mode_w: Default::default(),
790 mag_filter: Default::default(),
791 min_filter: Default::default(),
792 mipmap_filter: Default::default(),
793 lod_min_clamp: 0.0,
794 lod_max_clamp: 32.0,
795 compare: None,
796 anisotropy_clamp: 1,
797 border_color: None,
798 label: None,
799 }
800 }
801}
802
803impl ImageSamplerDescriptor {
804 #[inline]
806 pub fn linear() -> ImageSamplerDescriptor {
807 ImageSamplerDescriptor {
808 mag_filter: ImageFilterMode::Linear,
809 min_filter: ImageFilterMode::Linear,
810 mipmap_filter: ImageFilterMode::Linear,
811 ..Default::default()
812 }
813 }
814
815 #[inline]
817 pub fn nearest() -> ImageSamplerDescriptor {
818 ImageSamplerDescriptor {
819 mag_filter: ImageFilterMode::Nearest,
820 min_filter: ImageFilterMode::Nearest,
821 mipmap_filter: ImageFilterMode::Nearest,
822 ..Default::default()
823 }
824 }
825
826 #[inline]
828 pub fn set_filter(&mut self, filter: ImageFilterMode) -> &mut Self {
829 self.mag_filter = filter;
830 self.min_filter = filter;
831 self.mipmap_filter = filter;
832 self
833 }
834
835 #[inline]
837 pub fn set_address_mode(&mut self, address_mode: ImageAddressMode) -> &mut Self {
838 self.address_mode_u = address_mode;
839 self.address_mode_v = address_mode;
840 self.address_mode_w = address_mode;
841 self
842 }
843
844 #[inline]
848 pub fn set_anisotropic_filter(&mut self, anisotropy_clamp: u16) -> &mut Self {
849 self.mag_filter = ImageFilterMode::Linear;
850 self.min_filter = ImageFilterMode::Linear;
851 self.mipmap_filter = ImageFilterMode::Linear;
852 self.anisotropy_clamp = anisotropy_clamp;
853 self
854 }
855
856 pub fn as_wgpu(&self) -> SamplerDescriptor<Option<&str>> {
857 SamplerDescriptor {
858 label: self.label.as_deref(),
859 address_mode_u: self.address_mode_u.into(),
860 address_mode_v: self.address_mode_v.into(),
861 address_mode_w: self.address_mode_w.into(),
862 mag_filter: self.mag_filter.into(),
863 min_filter: self.min_filter.into(),
864 mipmap_filter: self.mipmap_filter.into(),
865 lod_min_clamp: self.lod_min_clamp,
866 lod_max_clamp: self.lod_max_clamp,
867 compare: self.compare.map(Into::into),
868 anisotropy_clamp: self.anisotropy_clamp,
869 border_color: self.border_color.map(Into::into),
870 }
871 }
872}
873
874impl From<ImageAddressMode> for AddressMode {
875 fn from(value: ImageAddressMode) -> Self {
876 match value {
877 ImageAddressMode::ClampToEdge => AddressMode::ClampToEdge,
878 ImageAddressMode::Repeat => AddressMode::Repeat,
879 ImageAddressMode::MirrorRepeat => AddressMode::MirrorRepeat,
880 ImageAddressMode::ClampToBorder => AddressMode::ClampToBorder,
881 }
882 }
883}
884
885impl From<ImageFilterMode> for FilterMode {
886 fn from(value: ImageFilterMode) -> Self {
887 match value {
888 ImageFilterMode::Nearest => FilterMode::Nearest,
889 ImageFilterMode::Linear => FilterMode::Linear,
890 }
891 }
892}
893
894impl From<ImageCompareFunction> for CompareFunction {
895 fn from(value: ImageCompareFunction) -> Self {
896 match value {
897 ImageCompareFunction::Never => CompareFunction::Never,
898 ImageCompareFunction::Less => CompareFunction::Less,
899 ImageCompareFunction::Equal => CompareFunction::Equal,
900 ImageCompareFunction::LessEqual => CompareFunction::LessEqual,
901 ImageCompareFunction::Greater => CompareFunction::Greater,
902 ImageCompareFunction::NotEqual => CompareFunction::NotEqual,
903 ImageCompareFunction::GreaterEqual => CompareFunction::GreaterEqual,
904 ImageCompareFunction::Always => CompareFunction::Always,
905 }
906 }
907}
908
909impl From<ImageSamplerBorderColor> for SamplerBorderColor {
910 fn from(value: ImageSamplerBorderColor) -> Self {
911 match value {
912 ImageSamplerBorderColor::TransparentBlack => SamplerBorderColor::TransparentBlack,
913 ImageSamplerBorderColor::OpaqueBlack => SamplerBorderColor::OpaqueBlack,
914 ImageSamplerBorderColor::OpaqueWhite => SamplerBorderColor::OpaqueWhite,
915 ImageSamplerBorderColor::Zero => SamplerBorderColor::Zero,
916 }
917 }
918}
919
920impl From<AddressMode> for ImageAddressMode {
921 fn from(value: AddressMode) -> Self {
922 match value {
923 AddressMode::ClampToEdge => ImageAddressMode::ClampToEdge,
924 AddressMode::Repeat => ImageAddressMode::Repeat,
925 AddressMode::MirrorRepeat => ImageAddressMode::MirrorRepeat,
926 AddressMode::ClampToBorder => ImageAddressMode::ClampToBorder,
927 }
928 }
929}
930
931impl From<FilterMode> for ImageFilterMode {
932 fn from(value: FilterMode) -> Self {
933 match value {
934 FilterMode::Nearest => ImageFilterMode::Nearest,
935 FilterMode::Linear => ImageFilterMode::Linear,
936 }
937 }
938}
939
940impl From<CompareFunction> for ImageCompareFunction {
941 fn from(value: CompareFunction) -> Self {
942 match value {
943 CompareFunction::Never => ImageCompareFunction::Never,
944 CompareFunction::Less => ImageCompareFunction::Less,
945 CompareFunction::Equal => ImageCompareFunction::Equal,
946 CompareFunction::LessEqual => ImageCompareFunction::LessEqual,
947 CompareFunction::Greater => ImageCompareFunction::Greater,
948 CompareFunction::NotEqual => ImageCompareFunction::NotEqual,
949 CompareFunction::GreaterEqual => ImageCompareFunction::GreaterEqual,
950 CompareFunction::Always => ImageCompareFunction::Always,
951 }
952 }
953}
954
955impl From<SamplerBorderColor> for ImageSamplerBorderColor {
956 fn from(value: SamplerBorderColor) -> Self {
957 match value {
958 SamplerBorderColor::TransparentBlack => ImageSamplerBorderColor::TransparentBlack,
959 SamplerBorderColor::OpaqueBlack => ImageSamplerBorderColor::OpaqueBlack,
960 SamplerBorderColor::OpaqueWhite => ImageSamplerBorderColor::OpaqueWhite,
961 SamplerBorderColor::Zero => ImageSamplerBorderColor::Zero,
962 }
963 }
964}
965
966impl From<SamplerDescriptor<Option<&str>>> for ImageSamplerDescriptor {
967 fn from(value: SamplerDescriptor<Option<&str>>) -> Self {
968 ImageSamplerDescriptor {
969 label: value.label.map(ToString::to_string),
970 address_mode_u: value.address_mode_u.into(),
971 address_mode_v: value.address_mode_v.into(),
972 address_mode_w: value.address_mode_w.into(),
973 mag_filter: value.mag_filter.into(),
974 min_filter: value.min_filter.into(),
975 mipmap_filter: value.mipmap_filter.into(),
976 lod_min_clamp: value.lod_min_clamp,
977 lod_max_clamp: value.lod_max_clamp,
978 compare: value.compare.map(Into::into),
979 anisotropy_clamp: value.anisotropy_clamp,
980 border_color: value.border_color.map(Into::into),
981 }
982 }
983}
984
985impl Default for Image {
986 fn default() -> Self {
988 let mut image = Image::default_uninit();
989 image.data = Some(vec![
990 255;
991 image
992 .texture_descriptor
993 .format
994 .pixel_size()
995 .unwrap_or(0)
996 ]);
997 image
998 }
999}
1000
1001impl Image {
1002 pub fn new(
1008 size: Extent3d,
1009 dimension: TextureDimension,
1010 data: Vec<u8>,
1011 format: TextureFormat,
1012 asset_usage: RenderAssetUsages,
1013 ) -> Self {
1014 if let Ok(pixel_size) = format.pixel_size() {
1015 debug_assert_eq!(
1016 size.volume() * pixel_size,
1017 data.len(),
1018 "Pixel data, size and format have to match",
1019 );
1020 }
1021 let mut image = Image::new_uninit(size, dimension, format, asset_usage);
1022 image.data = Some(data);
1023 image
1024 }
1025
1026 pub fn new_uninit(
1028 size: Extent3d,
1029 dimension: TextureDimension,
1030 format: TextureFormat,
1031 asset_usage: RenderAssetUsages,
1032 ) -> Self {
1033 Image {
1034 data: None,
1035 data_order: TextureDataOrder::default(),
1036 texture_descriptor: TextureDescriptor {
1037 size,
1038 format,
1039 dimension,
1040 label: None,
1041 mip_level_count: 1,
1042 sample_count: 1,
1043 usage: TextureUsages::TEXTURE_BINDING
1044 | TextureUsages::COPY_DST
1045 | TextureUsages::COPY_SRC,
1046 view_formats: &[],
1047 },
1048 sampler: ImageSampler::Default,
1049 texture_view_descriptor: None,
1050 asset_usage,
1051 copy_on_resize: false,
1052 }
1053 }
1054
1055 pub fn transparent() -> Image {
1059 let format = TextureFormat::bevy_default();
1063 if let Ok(pixel_size) = format.pixel_size() {
1064 debug_assert!(pixel_size == 4);
1065 }
1066 let data = vec![255, 255, 255, 0];
1067 Image::new(
1068 Extent3d::default(),
1069 TextureDimension::D2,
1070 data,
1071 format,
1072 RenderAssetUsages::default(),
1073 )
1074 }
1075 pub fn default_uninit() -> Image {
1077 Image::new_uninit(
1078 Extent3d::default(),
1079 TextureDimension::D2,
1080 TextureFormat::bevy_default(),
1081 RenderAssetUsages::default(),
1082 )
1083 }
1084
1085 pub fn new_fill(
1091 size: Extent3d,
1092 dimension: TextureDimension,
1093 pixel: &[u8],
1094 format: TextureFormat,
1095 asset_usage: RenderAssetUsages,
1096 ) -> Self {
1097 let mut image = Image::new_uninit(size, dimension, format, asset_usage);
1098 if let Ok(pixel_size) = image.texture_descriptor.format.pixel_size()
1099 && pixel_size > 0
1100 {
1101 let byte_len = pixel_size * size.volume();
1102 debug_assert_eq!(
1103 pixel.len() % pixel_size,
1104 0,
1105 "Must not have incomplete pixel data (pixel size is {}B).",
1106 pixel_size,
1107 );
1108 debug_assert!(
1109 pixel.len() <= byte_len,
1110 "Fill data must fit within pixel buffer (expected {byte_len}B).",
1111 );
1112 let data = pixel.iter().copied().cycle().take(byte_len).collect();
1113 image.data = Some(data);
1114 }
1115 image
1116 }
1117
1118 pub fn new_target_texture(
1138 width: u32,
1139 height: u32,
1140 format: TextureFormat,
1141 view_format: Option<TextureFormat>,
1142 ) -> Self {
1143 let size = Extent3d {
1144 width,
1145 height,
1146 ..Default::default()
1147 };
1148 let usage = TextureUsages::TEXTURE_BINDING
1150 | TextureUsages::COPY_DST
1151 | TextureUsages::RENDER_ATTACHMENT;
1152 let data = vec![
1154 0;
1155 format.pixel_size().expect(
1156 "Failed to create Image: can't get pixel size for this TextureFormat"
1157 ) * size.volume()
1158 ];
1159
1160 Image {
1161 data: Some(data),
1162 data_order: TextureDataOrder::default(),
1163 texture_descriptor: TextureDescriptor {
1164 size,
1165 format,
1166 dimension: TextureDimension::D2,
1167 label: None,
1168 mip_level_count: 1,
1169 sample_count: 1,
1170 usage,
1171 view_formats: match view_format {
1172 Some(_) => format.srgb_view_formats(),
1173 None => &[],
1174 },
1175 },
1176 sampler: ImageSampler::Default,
1177 texture_view_descriptor: view_format.map(|f| TextureViewDescriptor {
1178 format: Some(f),
1179 ..Default::default()
1180 }),
1181 asset_usage: RenderAssetUsages::default(),
1182 copy_on_resize: true,
1183 }
1184 }
1185
1186 #[inline]
1188 pub fn width(&self) -> u32 {
1189 self.texture_descriptor.size.width
1190 }
1191
1192 #[inline]
1194 pub fn height(&self) -> u32 {
1195 self.texture_descriptor.size.height
1196 }
1197
1198 #[inline]
1200 pub fn aspect_ratio(&self) -> AspectRatio {
1201 AspectRatio::try_from_pixels(self.width(), self.height()).expect(
1202 "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
1203 )
1204 }
1205
1206 #[inline]
1208 pub fn size_f32(&self) -> Vec2 {
1209 Vec2::new(self.width() as f32, self.height() as f32)
1210 }
1211
1212 #[inline]
1214 pub fn size(&self) -> UVec2 {
1215 UVec2::new(self.width(), self.height())
1216 }
1217
1218 pub fn resize(&mut self, size: Extent3d) {
1223 self.texture_descriptor.size = size;
1224 if let Some(ref mut data) = self.data
1225 && let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1226 {
1227 data.resize(size.volume() * pixel_size, 0);
1228 }
1229 }
1230
1231 pub fn reinterpret_size(
1234 &mut self,
1235 new_size: Extent3d,
1236 ) -> Result<(), TextureReinterpretationError> {
1237 if new_size.volume() != self.texture_descriptor.size.volume() {
1238 return Err(TextureReinterpretationError::IncompatibleSizes {
1239 old: self.texture_descriptor.size,
1240 new: new_size,
1241 });
1242 }
1243
1244 self.texture_descriptor.size = new_size;
1245 Ok(())
1246 }
1247
1248 pub fn resize_in_place(&mut self, new_size: Extent3d) {
1253 if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size() {
1254 let old_size = self.texture_descriptor.size;
1255 let byte_len = pixel_size * new_size.volume();
1256 self.texture_descriptor.size = new_size;
1257
1258 let Some(ref mut data) = self.data else {
1259 self.copy_on_resize = true;
1260 return;
1261 };
1262
1263 let mut new: Vec<u8> = vec![0; byte_len];
1264
1265 let copy_width = old_size.width.min(new_size.width) as usize;
1266 let copy_height = old_size.height.min(new_size.height) as usize;
1267 let copy_depth = old_size
1268 .depth_or_array_layers
1269 .min(new_size.depth_or_array_layers) as usize;
1270
1271 let old_row_stride = old_size.width as usize * pixel_size;
1272 let old_layer_stride = old_size.height as usize * old_row_stride;
1273
1274 let new_row_stride = new_size.width as usize * pixel_size;
1275 let new_layer_stride = new_size.height as usize * new_row_stride;
1276
1277 for z in 0..copy_depth {
1278 for y in 0..copy_height {
1279 let old_offset = z * old_layer_stride + y * old_row_stride;
1280 let new_offset = z * new_layer_stride + y * new_row_stride;
1281
1282 let old_range = (old_offset)..(old_offset + copy_width * pixel_size);
1283 let new_range = (new_offset)..(new_offset + copy_width * pixel_size);
1284
1285 new[new_range].copy_from_slice(&data[old_range]);
1286 }
1287 }
1288
1289 self.data = Some(new);
1290 }
1291 }
1292
1293 pub fn reinterpret_stacked_2d_as_array(
1301 &mut self,
1302 layers: u32,
1303 ) -> Result<(), TextureReinterpretationError> {
1304 if self.texture_descriptor.dimension != TextureDimension::D2 {
1306 return Err(TextureReinterpretationError::WrongDimension);
1307 }
1308 if self.texture_descriptor.size.depth_or_array_layers != 1 {
1309 return Err(TextureReinterpretationError::InvalidLayerCount);
1310 }
1311 if !self.height().is_multiple_of(layers) {
1312 return Err(TextureReinterpretationError::HeightNotDivisibleByLayers {
1313 height: self.height(),
1314 layers,
1315 });
1316 }
1317
1318 self.reinterpret_size(Extent3d {
1319 width: self.width(),
1320 height: self.height() / layers,
1321 depth_or_array_layers: layers,
1322 })?;
1323
1324 Ok(())
1325 }
1326
1327 pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
1336 self.clone()
1337 .try_into_dynamic()
1338 .ok()
1339 .and_then(|img| match new_format {
1340 TextureFormat::R8Unorm => {
1341 Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
1342 }
1343 TextureFormat::Rg8Unorm => Some((
1344 image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
1345 false,
1346 )),
1347 TextureFormat::Rgba8UnormSrgb => {
1348 Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
1349 }
1350 _ => None,
1351 })
1352 .map(|(dyn_img, is_srgb)| Self::from_dynamic(dyn_img, is_srgb, self.asset_usage))
1353 }
1354
1355 pub fn from_buffer(
1358 buffer: &[u8],
1359 image_type: ImageType,
1360 #[cfg_attr(
1361 not(any(feature = "basis-universal", feature = "dds", feature = "ktx2")),
1362 expect(unused_variables, reason = "only used with certain features")
1363 )]
1364 supported_compressed_formats: CompressedImageFormats,
1365 is_srgb: bool,
1366 image_sampler: ImageSampler,
1367 asset_usage: RenderAssetUsages,
1368 ) -> Result<Image, TextureError> {
1369 let format = image_type.to_image_format()?;
1370
1371 let mut image = match format {
1378 #[cfg(feature = "basis-universal")]
1379 ImageFormat::Basis => {
1380 basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
1381 }
1382 #[cfg(feature = "dds")]
1383 ImageFormat::Dds => dds_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?,
1384 #[cfg(feature = "ktx2")]
1385 ImageFormat::Ktx2 => {
1386 ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
1387 }
1388 #[expect(
1389 clippy::allow_attributes,
1390 reason = "`unreachable_patterns` may not always lint"
1391 )]
1392 #[allow(
1393 unreachable_patterns,
1394 reason = "The wildcard pattern may be unreachable if only the specially-handled formats are enabled; however, the wildcard pattern is needed for any formats not specially handled"
1395 )]
1396 _ => {
1397 let image_crate_format = format
1398 .as_image_crate_format()
1399 .ok_or_else(|| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?;
1400 let mut reader = image::ImageReader::new(std::io::Cursor::new(buffer));
1401 reader.set_format(image_crate_format);
1402 reader.no_limits();
1403 let dyn_img = reader.decode()?;
1404 Self::from_dynamic(dyn_img, is_srgb, asset_usage)
1405 }
1406 };
1407 image.sampler = image_sampler;
1408 Ok(image)
1409 }
1410
1411 pub fn is_compressed(&self) -> bool {
1413 let format_description = self.texture_descriptor.format;
1414 format_description
1415 .required_features()
1416 .contains(Features::TEXTURE_COMPRESSION_ASTC)
1417 || format_description
1418 .required_features()
1419 .contains(Features::TEXTURE_COMPRESSION_BC)
1420 || format_description
1421 .required_features()
1422 .contains(Features::TEXTURE_COMPRESSION_ETC2)
1423 }
1424
1425 #[inline(always)]
1431 pub fn pixel_data_offset(&self, coords: UVec3) -> Option<usize> {
1432 let width = self.texture_descriptor.size.width;
1433 let height = self.texture_descriptor.size.height;
1434 let depth = self.texture_descriptor.size.depth_or_array_layers;
1435
1436 let pixel_size = self.texture_descriptor.format.pixel_size().ok()?;
1437 let pixel_offset = match self.texture_descriptor.dimension {
1438 TextureDimension::D3 | TextureDimension::D2 => {
1439 if coords.x >= width || coords.y >= height || coords.z >= depth {
1440 return None;
1441 }
1442 coords.z * height * width + coords.y * width + coords.x
1443 }
1444 TextureDimension::D1 => {
1445 if coords.x >= width {
1446 return None;
1447 }
1448 coords.x
1449 }
1450 };
1451
1452 Some(pixel_offset as usize * pixel_size)
1453 }
1454
1455 #[inline(always)]
1457 pub fn pixel_bytes(&self, coords: UVec3) -> Option<&[u8]> {
1458 let len = self.texture_descriptor.format.pixel_size().ok()?;
1459 let data = self.data.as_ref()?;
1460 self.pixel_data_offset(coords)
1461 .map(|start| &data[start..(start + len)])
1462 }
1463
1464 #[inline(always)]
1466 pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Option<&mut [u8]> {
1467 let len = self.texture_descriptor.format.pixel_size().ok()?;
1468 let offset = self.pixel_data_offset(coords);
1469 let data = self.data.as_mut()?;
1470 offset.map(|start| &mut data[start..(start + len)])
1471 }
1472
1473 pub fn clear(&mut self, pixel: &[u8]) {
1478 if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1479 && pixel_size > 0
1480 {
1481 let byte_len = pixel_size * self.texture_descriptor.size.volume();
1482 debug_assert_eq!(
1483 pixel.len() % pixel_size,
1484 0,
1485 "Must not have incomplete pixel data (pixel size is {}B).",
1486 pixel_size,
1487 );
1488 debug_assert!(
1489 pixel.len() <= byte_len,
1490 "Clear data must fit within pixel buffer (expected {byte_len}B).",
1491 );
1492 if let Some(data) = self.data.as_mut() {
1493 for pixel_data in data.chunks_mut(pixel_size) {
1494 pixel_data.copy_from_slice(pixel);
1495 }
1496 }
1497 }
1498 }
1499
1500 #[inline(always)]
1504 pub fn get_color_at_1d(&self, x: u32) -> Result<Color, TextureAccessError> {
1505 if self.texture_descriptor.dimension != TextureDimension::D1 {
1506 return Err(TextureAccessError::WrongDimension);
1507 }
1508 self.get_color_at_internal(UVec3::new(x, 0, 0))
1509 }
1510
1511 #[inline(always)]
1535 pub fn get_color_at(&self, x: u32, y: u32) -> Result<Color, TextureAccessError> {
1536 if self.texture_descriptor.dimension != TextureDimension::D2 {
1537 return Err(TextureAccessError::WrongDimension);
1538 }
1539 self.get_color_at_internal(UVec3::new(x, y, 0))
1540 }
1541
1542 #[inline(always)]
1546 pub fn get_color_at_3d(&self, x: u32, y: u32, z: u32) -> Result<Color, TextureAccessError> {
1547 match (
1548 self.texture_descriptor.dimension,
1549 self.texture_descriptor.size.depth_or_array_layers,
1550 ) {
1551 (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1552 self.get_color_at_internal(UVec3::new(x, y, z))
1553 }
1554 _ => Err(TextureAccessError::WrongDimension),
1555 }
1556 }
1557
1558 #[inline(always)]
1562 pub fn set_color_at_1d(&mut self, x: u32, color: Color) -> Result<(), TextureAccessError> {
1563 if self.texture_descriptor.dimension != TextureDimension::D1 {
1564 return Err(TextureAccessError::WrongDimension);
1565 }
1566 self.set_color_at_internal(UVec3::new(x, 0, 0), color)
1567 }
1568
1569 #[inline(always)]
1591 pub fn set_color_at(&mut self, x: u32, y: u32, color: Color) -> Result<(), TextureAccessError> {
1592 if self.texture_descriptor.dimension != TextureDimension::D2 {
1593 return Err(TextureAccessError::WrongDimension);
1594 }
1595 self.set_color_at_internal(UVec3::new(x, y, 0), color)
1596 }
1597
1598 #[inline(always)]
1602 pub fn set_color_at_3d(
1603 &mut self,
1604 x: u32,
1605 y: u32,
1606 z: u32,
1607 color: Color,
1608 ) -> Result<(), TextureAccessError> {
1609 match (
1610 self.texture_descriptor.dimension,
1611 self.texture_descriptor.size.depth_or_array_layers,
1612 ) {
1613 (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1614 self.set_color_at_internal(UVec3::new(x, y, z), color)
1615 }
1616 _ => Err(TextureAccessError::WrongDimension),
1617 }
1618 }
1619
1620 #[inline(always)]
1621 fn get_color_at_internal(&self, coords: UVec3) -> Result<Color, TextureAccessError> {
1622 let Some(bytes) = self.pixel_bytes(coords) else {
1623 return Err(TextureAccessError::OutOfBounds {
1624 x: coords.x,
1625 y: coords.y,
1626 z: coords.z,
1627 });
1628 };
1629
1630 match self.texture_descriptor.format {
1633 TextureFormat::Rgba8UnormSrgb => Ok(Color::srgba(
1634 bytes[0] as f32 / u8::MAX as f32,
1635 bytes[1] as f32 / u8::MAX as f32,
1636 bytes[2] as f32 / u8::MAX as f32,
1637 bytes[3] as f32 / u8::MAX as f32,
1638 )),
1639 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => Ok(Color::linear_rgba(
1640 bytes[0] as f32 / u8::MAX as f32,
1641 bytes[1] as f32 / u8::MAX as f32,
1642 bytes[2] as f32 / u8::MAX as f32,
1643 bytes[3] as f32 / u8::MAX as f32,
1644 )),
1645 TextureFormat::Bgra8UnormSrgb => Ok(Color::srgba(
1646 bytes[2] as f32 / u8::MAX as f32,
1647 bytes[1] as f32 / u8::MAX as f32,
1648 bytes[0] as f32 / u8::MAX as f32,
1649 bytes[3] as f32 / u8::MAX as f32,
1650 )),
1651 TextureFormat::Bgra8Unorm => Ok(Color::linear_rgba(
1652 bytes[2] as f32 / u8::MAX as f32,
1653 bytes[1] as f32 / u8::MAX as f32,
1654 bytes[0] as f32 / u8::MAX as f32,
1655 bytes[3] as f32 / u8::MAX as f32,
1656 )),
1657 TextureFormat::Rgba32Float => Ok(Color::linear_rgba(
1658 f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1659 f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1660 f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1661 f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1662 )),
1663 TextureFormat::Rgba16Float => Ok(Color::linear_rgba(
1664 half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32(),
1665 half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32(),
1666 half::f16::from_le_bytes([bytes[4], bytes[5]]).to_f32(),
1667 half::f16::from_le_bytes([bytes[6], bytes[7]]).to_f32(),
1668 )),
1669 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1670 let (r, g, b, a) = (
1671 u16::from_le_bytes([bytes[0], bytes[1]]),
1672 u16::from_le_bytes([bytes[2], bytes[3]]),
1673 u16::from_le_bytes([bytes[4], bytes[5]]),
1674 u16::from_le_bytes([bytes[6], bytes[7]]),
1675 );
1676 Ok(Color::linear_rgba(
1677 (r as f64 / u16::MAX as f64) as f32,
1679 (g as f64 / u16::MAX as f64) as f32,
1680 (b as f64 / u16::MAX as f64) as f32,
1681 (a as f64 / u16::MAX as f64) as f32,
1682 ))
1683 }
1684 TextureFormat::Rgba32Uint => {
1685 let (r, g, b, a) = (
1686 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1687 u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1688 u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1689 u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1690 );
1691 Ok(Color::linear_rgba(
1692 (r as f64 / u32::MAX as f64) as f32,
1694 (g as f64 / u32::MAX as f64) as f32,
1695 (b as f64 / u32::MAX as f64) as f32,
1696 (a as f64 / u32::MAX as f64) as f32,
1697 ))
1698 }
1699 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1702 let x = bytes[0] as f32 / u8::MAX as f32;
1703 Ok(Color::linear_rgb(x, x, x))
1704 }
1705 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1706 let x = u16::from_le_bytes([bytes[0], bytes[1]]);
1707 let x = (x as f64 / u16::MAX as f64) as f32;
1709 Ok(Color::linear_rgb(x, x, x))
1710 }
1711 TextureFormat::R32Uint => {
1712 let x = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1713 let x = (x as f64 / u32::MAX as f64) as f32;
1715 Ok(Color::linear_rgb(x, x, x))
1716 }
1717 TextureFormat::R16Float => {
1718 let x = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1719 Ok(Color::linear_rgb(x, x, x))
1720 }
1721 TextureFormat::R32Float => {
1722 let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1723 Ok(Color::linear_rgb(x, x, x))
1724 }
1725 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1726 let r = bytes[0] as f32 / u8::MAX as f32;
1727 let g = bytes[1] as f32 / u8::MAX as f32;
1728 Ok(Color::linear_rgb(r, g, 0.0))
1729 }
1730 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1731 let r = u16::from_le_bytes([bytes[0], bytes[1]]);
1732 let g = u16::from_le_bytes([bytes[2], bytes[3]]);
1733 let r = (r as f64 / u16::MAX as f64) as f32;
1735 let g = (g as f64 / u16::MAX as f64) as f32;
1736 Ok(Color::linear_rgb(r, g, 0.0))
1737 }
1738 TextureFormat::Rg32Uint => {
1739 let r = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1740 let g = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1741 let r = (r as f64 / u32::MAX as f64) as f32;
1743 let g = (g as f64 / u32::MAX as f64) as f32;
1744 Ok(Color::linear_rgb(r, g, 0.0))
1745 }
1746 TextureFormat::Rg16Float => {
1747 let r = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1748 let g = half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32();
1749 Ok(Color::linear_rgb(r, g, 0.0))
1750 }
1751 TextureFormat::Rg32Float => {
1752 let r = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1753 let g = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1754 Ok(Color::linear_rgb(r, g, 0.0))
1755 }
1756 _ => Err(TextureAccessError::UnsupportedTextureFormat(
1757 self.texture_descriptor.format,
1758 )),
1759 }
1760 }
1761
1762 #[inline(always)]
1763 fn set_color_at_internal(
1764 &mut self,
1765 coords: UVec3,
1766 color: Color,
1767 ) -> Result<(), TextureAccessError> {
1768 let format = self.texture_descriptor.format;
1769
1770 let Some(bytes) = self.pixel_bytes_mut(coords) else {
1771 return Err(TextureAccessError::OutOfBounds {
1772 x: coords.x,
1773 y: coords.y,
1774 z: coords.z,
1775 });
1776 };
1777
1778 match format {
1781 TextureFormat::Rgba8UnormSrgb => {
1782 let [r, g, b, a] = Srgba::from(color).to_f32_array();
1783 bytes[0] = (r * u8::MAX as f32) as u8;
1784 bytes[1] = (g * u8::MAX as f32) as u8;
1785 bytes[2] = (b * u8::MAX as f32) as u8;
1786 bytes[3] = (a * u8::MAX as f32) as u8;
1787 }
1788 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => {
1789 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1790 bytes[0] = (r * u8::MAX as f32) as u8;
1791 bytes[1] = (g * u8::MAX as f32) as u8;
1792 bytes[2] = (b * u8::MAX as f32) as u8;
1793 bytes[3] = (a * u8::MAX as f32) as u8;
1794 }
1795 TextureFormat::Bgra8UnormSrgb => {
1796 let [r, g, b, a] = Srgba::from(color).to_f32_array();
1797 bytes[0] = (b * u8::MAX as f32) as u8;
1798 bytes[1] = (g * u8::MAX as f32) as u8;
1799 bytes[2] = (r * u8::MAX as f32) as u8;
1800 bytes[3] = (a * u8::MAX as f32) as u8;
1801 }
1802 TextureFormat::Bgra8Unorm => {
1803 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1804 bytes[0] = (b * u8::MAX as f32) as u8;
1805 bytes[1] = (g * u8::MAX as f32) as u8;
1806 bytes[2] = (r * u8::MAX as f32) as u8;
1807 bytes[3] = (a * u8::MAX as f32) as u8;
1808 }
1809 TextureFormat::Rgba16Float => {
1810 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1811 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1812 bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1813 bytes[4..6].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(b)));
1814 bytes[6..8].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(a)));
1815 }
1816 TextureFormat::Rgba32Float => {
1817 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1818 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1819 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1820 bytes[8..12].copy_from_slice(&f32::to_le_bytes(b));
1821 bytes[12..16].copy_from_slice(&f32::to_le_bytes(a));
1822 }
1823 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1824 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1825 let [r, g, b, a] = [
1826 (r * u16::MAX as f32) as u16,
1827 (g * u16::MAX as f32) as u16,
1828 (b * u16::MAX as f32) as u16,
1829 (a * u16::MAX as f32) as u16,
1830 ];
1831 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1832 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1833 bytes[4..6].copy_from_slice(&u16::to_le_bytes(b));
1834 bytes[6..8].copy_from_slice(&u16::to_le_bytes(a));
1835 }
1836 TextureFormat::Rgba32Uint => {
1837 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1838 let [r, g, b, a] = [
1839 (r * u32::MAX as f32) as u32,
1840 (g * u32::MAX as f32) as u32,
1841 (b * u32::MAX as f32) as u32,
1842 (a * u32::MAX as f32) as u32,
1843 ];
1844 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1845 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1846 bytes[8..12].copy_from_slice(&u32::to_le_bytes(b));
1847 bytes[12..16].copy_from_slice(&u32::to_le_bytes(a));
1848 }
1849 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1850 let linear = LinearRgba::from(color);
1852 let luminance = Xyza::from(linear).y;
1853 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1854 bytes[0] = (r * u8::MAX as f32) as u8;
1855 }
1856 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1857 let linear = LinearRgba::from(color);
1859 let luminance = Xyza::from(linear).y;
1860 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1861 let r = (r * u16::MAX as f32) as u16;
1862 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1863 }
1864 TextureFormat::R32Uint => {
1865 let linear = LinearRgba::from(color);
1867 let luminance = Xyza::from(linear).y;
1868 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1869 let r = (r as f64 * u32::MAX as f64) as u32;
1871 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1872 }
1873 TextureFormat::R16Float => {
1874 let linear = LinearRgba::from(color);
1876 let luminance = Xyza::from(linear).y;
1877 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1878 let x = half::f16::from_f32(r);
1879 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(x));
1880 }
1881 TextureFormat::R32Float => {
1882 let linear = LinearRgba::from(color);
1884 let luminance = Xyza::from(linear).y;
1885 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1886 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1887 }
1888 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1889 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1890 bytes[0] = (r * u8::MAX as f32) as u8;
1891 bytes[1] = (g * u8::MAX as f32) as u8;
1892 }
1893 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1894 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1895 let r = (r * u16::MAX as f32) as u16;
1896 let g = (g * u16::MAX as f32) as u16;
1897 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1898 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1899 }
1900 TextureFormat::Rg32Uint => {
1901 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1902 let r = (r as f64 * u32::MAX as f64) as u32;
1904 let g = (g as f64 * u32::MAX as f64) as u32;
1905 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1906 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1907 }
1908 TextureFormat::Rg16Float => {
1909 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1910 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1911 bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1912 }
1913 TextureFormat::Rg32Float => {
1914 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1915 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1916 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1917 }
1918 _ => {
1919 return Err(TextureAccessError::UnsupportedTextureFormat(
1920 self.texture_descriptor.format,
1921 ));
1922 }
1923 }
1924 Ok(())
1925 }
1926}
1927
1928#[derive(Clone, Copy, Debug)]
1929pub enum DataFormat {
1930 Rgb,
1931 Rgba,
1932 Rrr,
1933 Rrrg,
1934 Rg,
1935}
1936
1937#[derive(Clone, Copy, Debug)]
1939pub enum TranscodeFormat {
1940 Etc1s,
1941 Uastc(DataFormat),
1942 R8UnormSrgb,
1944 Rg8UnormSrgb,
1946 Rgb8,
1948}
1949
1950#[derive(Error, Debug)]
1952pub enum TextureReinterpretationError {
1953 #[error("incompatible sizes: old = {old:?} new = {new:?}")]
1954 IncompatibleSizes { old: Extent3d, new: Extent3d },
1955 #[error("must be a 2d image")]
1956 WrongDimension,
1957 #[error("must not already be a layered image")]
1958 InvalidLayerCount,
1959 #[error("can not evenly divide height = {height} by layers = {layers}")]
1960 HeightNotDivisibleByLayers { height: u32, layers: u32 },
1961}
1962
1963#[derive(Error, Debug)]
1965pub enum TextureAccessError {
1966 #[error("out of bounds (x: {x}, y: {y}, z: {z})")]
1967 OutOfBounds { x: u32, y: u32, z: u32 },
1968 #[error("unsupported texture format: {0:?}")]
1969 UnsupportedTextureFormat(TextureFormat),
1970 #[error("attempt to access texture with different dimension")]
1971 WrongDimension,
1972}
1973
1974#[derive(Error, Debug)]
1976pub enum TextureError {
1977 #[error("invalid image mime type: {0}")]
1979 InvalidImageMimeType(String),
1980 #[error("invalid image extension: {0}")]
1982 InvalidImageExtension(String),
1983 #[error("failed to load an image: {0}")]
1985 ImageError(#[from] image::ImageError),
1986 #[error("unsupported texture format: {0}")]
1988 UnsupportedTextureFormat(String),
1989 #[error("supercompression not supported: {0}")]
1991 SuperCompressionNotSupported(String),
1992 #[error("failed to decompress an image: {0}")]
1994 SuperDecompressionError(String),
1995 #[error("invalid data: {0}")]
1997 InvalidData(String),
1998 #[error("transcode error: {0}")]
2000 TranscodeError(String),
2001 #[error("format requires transcoding: {0:?}")]
2003 FormatRequiresTranscodingError(TranscodeFormat),
2004 #[error("only cubemaps with six faces are supported")]
2006 IncompleteCubemap,
2007}
2008
2009#[derive(Debug)]
2011pub enum ImageType<'a> {
2012 MimeType(&'a str),
2014 Extension(&'a str),
2016 Format(ImageFormat),
2018}
2019
2020impl<'a> ImageType<'a> {
2021 pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
2022 match self {
2023 ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
2024 .ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
2025 ImageType::Extension(extension) => ImageFormat::from_extension(extension)
2026 .ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
2027 ImageType::Format(format) => Ok(*format),
2028 }
2029 }
2030}
2031
2032pub trait Volume {
2034 fn volume(&self) -> usize;
2035}
2036
2037impl Volume for Extent3d {
2038 fn volume(&self) -> usize {
2040 (self.width * self.height * self.depth_or_array_layers) as usize
2041 }
2042}
2043
2044pub trait TextureFormatPixelInfo {
2046 fn pixel_size(&self) -> Result<usize, TextureAccessError>;
2049}
2050
2051impl TextureFormatPixelInfo for TextureFormat {
2052 fn pixel_size(&self) -> Result<usize, TextureAccessError> {
2053 let info = self;
2054 match info.block_dimensions() {
2055 (1, 1) => Ok(info.block_copy_size(None).unwrap() as usize),
2056 _ => Err(TextureAccessError::UnsupportedTextureFormat(*self)),
2057 }
2058 }
2059}
2060
2061bitflags::bitflags! {
2062 #[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
2063 #[repr(transparent)]
2064 pub struct CompressedImageFormats: u32 {
2065 const NONE = 0;
2066 const ASTC_LDR = 1 << 0;
2067 const BC = 1 << 1;
2068 const ETC2 = 1 << 2;
2069 }
2070}
2071
2072impl CompressedImageFormats {
2073 pub fn from_features(features: Features) -> Self {
2074 let mut supported_compressed_formats = Self::default();
2075 if features.contains(Features::TEXTURE_COMPRESSION_ASTC) {
2076 supported_compressed_formats |= Self::ASTC_LDR;
2077 }
2078 if features.contains(Features::TEXTURE_COMPRESSION_BC) {
2079 supported_compressed_formats |= Self::BC;
2080 }
2081 if features.contains(Features::TEXTURE_COMPRESSION_ETC2) {
2082 supported_compressed_formats |= Self::ETC2;
2083 }
2084 supported_compressed_formats
2085 }
2086
2087 pub fn supports(&self, format: TextureFormat) -> bool {
2088 match format {
2089 TextureFormat::Bc1RgbaUnorm
2090 | TextureFormat::Bc1RgbaUnormSrgb
2091 | TextureFormat::Bc2RgbaUnorm
2092 | TextureFormat::Bc2RgbaUnormSrgb
2093 | TextureFormat::Bc3RgbaUnorm
2094 | TextureFormat::Bc3RgbaUnormSrgb
2095 | TextureFormat::Bc4RUnorm
2096 | TextureFormat::Bc4RSnorm
2097 | TextureFormat::Bc5RgUnorm
2098 | TextureFormat::Bc5RgSnorm
2099 | TextureFormat::Bc6hRgbUfloat
2100 | TextureFormat::Bc6hRgbFloat
2101 | TextureFormat::Bc7RgbaUnorm
2102 | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
2103 TextureFormat::Etc2Rgb8Unorm
2104 | TextureFormat::Etc2Rgb8UnormSrgb
2105 | TextureFormat::Etc2Rgb8A1Unorm
2106 | TextureFormat::Etc2Rgb8A1UnormSrgb
2107 | TextureFormat::Etc2Rgba8Unorm
2108 | TextureFormat::Etc2Rgba8UnormSrgb
2109 | TextureFormat::EacR11Unorm
2110 | TextureFormat::EacR11Snorm
2111 | TextureFormat::EacRg11Unorm
2112 | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
2113 TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
2114 _ => true,
2115 }
2116 }
2117}
2118
2119#[derive(Resource)]
2123pub struct CompressedImageFormatSupport(pub CompressedImageFormats);
2124
2125#[cfg(test)]
2126mod test {
2127 use super::*;
2128
2129 #[test]
2130 fn image_size() {
2131 let size = Extent3d {
2132 width: 200,
2133 height: 100,
2134 depth_or_array_layers: 1,
2135 };
2136 let image = Image::new_fill(
2137 size,
2138 TextureDimension::D2,
2139 &[0, 0, 0, 255],
2140 TextureFormat::Rgba8Unorm,
2141 RenderAssetUsages::MAIN_WORLD,
2142 );
2143 assert_eq!(
2144 Vec2::new(size.width as f32, size.height as f32),
2145 image.size_f32()
2146 );
2147 }
2148
2149 #[test]
2150 fn image_default_size() {
2151 let image = Image::default();
2152 assert_eq!(UVec2::ONE, image.size());
2153 assert_eq!(Vec2::ONE, image.size_f32());
2154 }
2155
2156 #[test]
2157 fn on_edge_pixel_is_invalid() {
2158 let image = Image::new_fill(
2159 Extent3d {
2160 width: 5,
2161 height: 10,
2162 depth_or_array_layers: 1,
2163 },
2164 TextureDimension::D2,
2165 &[0, 0, 0, 255],
2166 TextureFormat::Rgba8Unorm,
2167 RenderAssetUsages::MAIN_WORLD,
2168 );
2169 assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK)));
2170 assert!(matches!(
2171 image.get_color_at(0, 10),
2172 Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 })
2173 ));
2174 assert!(matches!(
2175 image.get_color_at(5, 10),
2176 Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 })
2177 ));
2178 }
2179
2180 #[test]
2181 fn get_set_pixel_2d_with_layers() {
2182 let mut image = Image::new_fill(
2183 Extent3d {
2184 width: 5,
2185 height: 10,
2186 depth_or_array_layers: 3,
2187 },
2188 TextureDimension::D2,
2189 &[0, 0, 0, 255],
2190 TextureFormat::Rgba8Unorm,
2191 RenderAssetUsages::MAIN_WORLD,
2192 );
2193 image.set_color_at_3d(0, 0, 0, Color::WHITE).unwrap();
2194 assert!(matches!(image.get_color_at_3d(0, 0, 0), Ok(Color::WHITE)));
2195 image.set_color_at_3d(2, 3, 1, Color::WHITE).unwrap();
2196 assert!(matches!(image.get_color_at_3d(2, 3, 1), Ok(Color::WHITE)));
2197 image.set_color_at_3d(4, 9, 2, Color::WHITE).unwrap();
2198 assert!(matches!(image.get_color_at_3d(4, 9, 2), Ok(Color::WHITE)));
2199 }
2200
2201 #[test]
2202 fn resize_in_place_2d_grow_and_shrink() {
2203 use bevy_color::ColorToPacked;
2204
2205 const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2206 const GROW_FILL: LinearRgba = LinearRgba::NONE;
2207
2208 let mut image = Image::new_fill(
2209 Extent3d {
2210 width: 2,
2211 height: 2,
2212 depth_or_array_layers: 1,
2213 },
2214 TextureDimension::D2,
2215 &INITIAL_FILL.to_u8_array(),
2216 TextureFormat::Rgba8Unorm,
2217 RenderAssetUsages::MAIN_WORLD,
2218 );
2219
2220 const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2223 (0, 1, LinearRgba::RED),
2224 (1, 1, LinearRgba::GREEN),
2225 (1, 0, LinearRgba::BLUE),
2226 ];
2227
2228 for (x, y, color) in &TEST_PIXELS {
2229 image.set_color_at(*x, *y, Color::from(*color)).unwrap();
2230 }
2231
2232 image.resize_in_place(Extent3d {
2234 width: 4,
2235 height: 4,
2236 depth_or_array_layers: 1,
2237 });
2238
2239 assert!(matches!(
2241 image.get_color_at(0, 0),
2242 Ok(Color::LinearRgba(INITIAL_FILL))
2243 ));
2244 for (x, y, color) in &TEST_PIXELS {
2245 assert_eq!(
2246 image.get_color_at(*x, *y).unwrap(),
2247 Color::LinearRgba(*color)
2248 );
2249 }
2250
2251 assert!(matches!(
2253 image.get_color_at(3, 3),
2254 Ok(Color::LinearRgba(GROW_FILL))
2255 ));
2256
2257 image.resize_in_place(Extent3d {
2259 width: 1,
2260 height: 1,
2261 depth_or_array_layers: 1,
2262 });
2263
2264 assert!(image.get_color_at(1, 1).is_err());
2266 }
2267
2268 #[test]
2269 fn resize_in_place_array_grow_and_shrink() {
2270 use bevy_color::ColorToPacked;
2271
2272 const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2273 const GROW_FILL: LinearRgba = LinearRgba::NONE;
2274 const LAYERS: u32 = 4;
2275
2276 let mut image = Image::new_fill(
2277 Extent3d {
2278 width: 2,
2279 height: 2,
2280 depth_or_array_layers: LAYERS,
2281 },
2282 TextureDimension::D2,
2283 &INITIAL_FILL.to_u8_array(),
2284 TextureFormat::Rgba8Unorm,
2285 RenderAssetUsages::MAIN_WORLD,
2286 );
2287
2288 const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2291 (0, 1, LinearRgba::RED),
2292 (1, 1, LinearRgba::GREEN),
2293 (1, 0, LinearRgba::BLUE),
2294 ];
2295
2296 for z in 0..LAYERS {
2297 for (x, y, color) in &TEST_PIXELS {
2298 image
2299 .set_color_at_3d(*x, *y, z, Color::from(*color))
2300 .unwrap();
2301 }
2302 }
2303
2304 image.resize_in_place(Extent3d {
2306 width: 4,
2307 height: 4,
2308 depth_or_array_layers: LAYERS + 1,
2309 });
2310
2311 assert!(matches!(
2313 image.get_color_at(0, 0),
2314 Ok(Color::LinearRgba(INITIAL_FILL))
2315 ));
2316 for z in 0..LAYERS {
2317 for (x, y, color) in &TEST_PIXELS {
2318 assert_eq!(
2319 image.get_color_at_3d(*x, *y, z).unwrap(),
2320 Color::LinearRgba(*color)
2321 );
2322 }
2323 }
2324
2325 for z in 0..(LAYERS + 1) {
2327 assert!(matches!(
2328 image.get_color_at_3d(3, 3, z),
2329 Ok(Color::LinearRgba(GROW_FILL))
2330 ));
2331 }
2332
2333 image.resize_in_place(Extent3d {
2335 width: 1,
2336 height: 1,
2337 depth_or_array_layers: 1,
2338 });
2339
2340 assert!(image.get_color_at_3d(1, 1, 0).is_err());
2342
2343 assert!(image.get_color_at_3d(0, 0, 1).is_err());
2345
2346 image.resize_in_place(Extent3d {
2348 width: 1,
2349 height: 1,
2350 depth_or_array_layers: 2,
2351 });
2352
2353 assert!(matches!(
2355 image.get_color_at_3d(0, 0, 1),
2356 Ok(Color::LinearRgba(GROW_FILL))
2357 ));
2358 }
2359
2360 #[test]
2361 fn image_clear() {
2362 let mut image = Image::new_fill(
2363 Extent3d {
2364 width: 32,
2365 height: 32,
2366 depth_or_array_layers: 1,
2367 },
2368 TextureDimension::D2,
2369 &[0; 4],
2370 TextureFormat::Rgba8Snorm,
2371 RenderAssetUsages::all(),
2372 );
2373
2374 assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 0));
2375
2376 image.clear(&[255; 4]);
2377
2378 assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 255));
2379 }
2380
2381 #[test]
2382 fn get_or_init_sampler_modifications() {
2383 let mut default_sampler = ImageSampler::Default;
2385 let my_sampler_in_a_loader = default_sampler
2387 .get_or_init_descriptor()
2388 .set_filter(ImageFilterMode::Linear)
2389 .set_address_mode(ImageAddressMode::Repeat);
2390
2391 assert_eq!(
2392 my_sampler_in_a_loader.address_mode_u,
2393 ImageAddressMode::Repeat
2394 );
2395 assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2396 }
2397
2398 #[test]
2399 fn get_or_init_sampler_anisotropy() {
2400 let mut default_sampler = ImageSampler::Default;
2402 let my_sampler_in_a_loader = default_sampler
2404 .get_or_init_descriptor()
2405 .set_anisotropic_filter(8);
2406
2407 assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2408 assert_eq!(my_sampler_in_a_loader.anisotropy_clamp, 8);
2409 }
2410}