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 image.asset_usage = asset_usage;
1409 Ok(image)
1410 }
1411
1412 pub fn is_compressed(&self) -> bool {
1414 let format_description = self.texture_descriptor.format;
1415 format_description
1416 .required_features()
1417 .contains(Features::TEXTURE_COMPRESSION_ASTC)
1418 || format_description
1419 .required_features()
1420 .contains(Features::TEXTURE_COMPRESSION_BC)
1421 || format_description
1422 .required_features()
1423 .contains(Features::TEXTURE_COMPRESSION_ETC2)
1424 }
1425
1426 #[inline(always)]
1432 pub fn pixel_data_offset(&self, coords: UVec3) -> Option<usize> {
1433 let width = self.texture_descriptor.size.width;
1434 let height = self.texture_descriptor.size.height;
1435 let depth = self.texture_descriptor.size.depth_or_array_layers;
1436
1437 let pixel_size = self.texture_descriptor.format.pixel_size().ok()?;
1438 let pixel_offset = match self.texture_descriptor.dimension {
1439 TextureDimension::D3 | TextureDimension::D2 => {
1440 if coords.x >= width || coords.y >= height || coords.z >= depth {
1441 return None;
1442 }
1443 coords.z * height * width + coords.y * width + coords.x
1444 }
1445 TextureDimension::D1 => {
1446 if coords.x >= width {
1447 return None;
1448 }
1449 coords.x
1450 }
1451 };
1452
1453 Some(pixel_offset as usize * pixel_size)
1454 }
1455
1456 #[inline(always)]
1458 pub fn pixel_bytes(&self, coords: UVec3) -> Option<&[u8]> {
1459 let len = self.texture_descriptor.format.pixel_size().ok()?;
1460 let data = self.data.as_ref()?;
1461 self.pixel_data_offset(coords)
1462 .map(|start| &data[start..(start + len)])
1463 }
1464
1465 #[inline(always)]
1467 pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Option<&mut [u8]> {
1468 let len = self.texture_descriptor.format.pixel_size().ok()?;
1469 let offset = self.pixel_data_offset(coords);
1470 let data = self.data.as_mut()?;
1471 offset.map(|start| &mut data[start..(start + len)])
1472 }
1473
1474 pub fn clear(&mut self, pixel: &[u8]) {
1479 if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1480 && pixel_size > 0
1481 {
1482 let byte_len = pixel_size * self.texture_descriptor.size.volume();
1483 debug_assert_eq!(
1484 pixel.len() % pixel_size,
1485 0,
1486 "Must not have incomplete pixel data (pixel size is {}B).",
1487 pixel_size,
1488 );
1489 debug_assert!(
1490 pixel.len() <= byte_len,
1491 "Clear data must fit within pixel buffer (expected {byte_len}B).",
1492 );
1493 if let Some(data) = self.data.as_mut() {
1494 for pixel_data in data.chunks_mut(pixel_size) {
1495 pixel_data.copy_from_slice(pixel);
1496 }
1497 }
1498 }
1499 }
1500
1501 #[inline(always)]
1505 pub fn get_color_at_1d(&self, x: u32) -> Result<Color, TextureAccessError> {
1506 if self.texture_descriptor.dimension != TextureDimension::D1 {
1507 return Err(TextureAccessError::WrongDimension);
1508 }
1509 self.get_color_at_internal(UVec3::new(x, 0, 0))
1510 }
1511
1512 #[inline(always)]
1536 pub fn get_color_at(&self, x: u32, y: u32) -> Result<Color, TextureAccessError> {
1537 if self.texture_descriptor.dimension != TextureDimension::D2 {
1538 return Err(TextureAccessError::WrongDimension);
1539 }
1540 self.get_color_at_internal(UVec3::new(x, y, 0))
1541 }
1542
1543 #[inline(always)]
1547 pub fn get_color_at_3d(&self, x: u32, y: u32, z: u32) -> Result<Color, TextureAccessError> {
1548 match (
1549 self.texture_descriptor.dimension,
1550 self.texture_descriptor.size.depth_or_array_layers,
1551 ) {
1552 (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1553 self.get_color_at_internal(UVec3::new(x, y, z))
1554 }
1555 _ => Err(TextureAccessError::WrongDimension),
1556 }
1557 }
1558
1559 #[inline(always)]
1563 pub fn set_color_at_1d(&mut self, x: u32, color: Color) -> Result<(), TextureAccessError> {
1564 if self.texture_descriptor.dimension != TextureDimension::D1 {
1565 return Err(TextureAccessError::WrongDimension);
1566 }
1567 self.set_color_at_internal(UVec3::new(x, 0, 0), color)
1568 }
1569
1570 #[inline(always)]
1592 pub fn set_color_at(&mut self, x: u32, y: u32, color: Color) -> Result<(), TextureAccessError> {
1593 if self.texture_descriptor.dimension != TextureDimension::D2 {
1594 return Err(TextureAccessError::WrongDimension);
1595 }
1596 self.set_color_at_internal(UVec3::new(x, y, 0), color)
1597 }
1598
1599 #[inline(always)]
1603 pub fn set_color_at_3d(
1604 &mut self,
1605 x: u32,
1606 y: u32,
1607 z: u32,
1608 color: Color,
1609 ) -> Result<(), TextureAccessError> {
1610 match (
1611 self.texture_descriptor.dimension,
1612 self.texture_descriptor.size.depth_or_array_layers,
1613 ) {
1614 (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1615 self.set_color_at_internal(UVec3::new(x, y, z), color)
1616 }
1617 _ => Err(TextureAccessError::WrongDimension),
1618 }
1619 }
1620
1621 #[inline(always)]
1622 fn get_color_at_internal(&self, coords: UVec3) -> Result<Color, TextureAccessError> {
1623 let Some(bytes) = self.pixel_bytes(coords) else {
1624 return Err(TextureAccessError::OutOfBounds {
1625 x: coords.x,
1626 y: coords.y,
1627 z: coords.z,
1628 });
1629 };
1630
1631 match self.texture_descriptor.format {
1634 TextureFormat::Rgba8UnormSrgb => Ok(Color::srgba(
1635 bytes[0] as f32 / u8::MAX as f32,
1636 bytes[1] as f32 / u8::MAX as f32,
1637 bytes[2] as f32 / u8::MAX as f32,
1638 bytes[3] as f32 / u8::MAX as f32,
1639 )),
1640 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => Ok(Color::linear_rgba(
1641 bytes[0] as f32 / u8::MAX as f32,
1642 bytes[1] as f32 / u8::MAX as f32,
1643 bytes[2] as f32 / u8::MAX as f32,
1644 bytes[3] as f32 / u8::MAX as f32,
1645 )),
1646 TextureFormat::Bgra8UnormSrgb => Ok(Color::srgba(
1647 bytes[2] as f32 / u8::MAX as f32,
1648 bytes[1] as f32 / u8::MAX as f32,
1649 bytes[0] as f32 / u8::MAX as f32,
1650 bytes[3] as f32 / u8::MAX as f32,
1651 )),
1652 TextureFormat::Bgra8Unorm => Ok(Color::linear_rgba(
1653 bytes[2] as f32 / u8::MAX as f32,
1654 bytes[1] as f32 / u8::MAX as f32,
1655 bytes[0] as f32 / u8::MAX as f32,
1656 bytes[3] as f32 / u8::MAX as f32,
1657 )),
1658 TextureFormat::Rgba32Float => Ok(Color::linear_rgba(
1659 f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1660 f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1661 f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1662 f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1663 )),
1664 TextureFormat::Rgba16Float => Ok(Color::linear_rgba(
1665 half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32(),
1666 half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32(),
1667 half::f16::from_le_bytes([bytes[4], bytes[5]]).to_f32(),
1668 half::f16::from_le_bytes([bytes[6], bytes[7]]).to_f32(),
1669 )),
1670 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1671 let (r, g, b, a) = (
1672 u16::from_le_bytes([bytes[0], bytes[1]]),
1673 u16::from_le_bytes([bytes[2], bytes[3]]),
1674 u16::from_le_bytes([bytes[4], bytes[5]]),
1675 u16::from_le_bytes([bytes[6], bytes[7]]),
1676 );
1677 Ok(Color::linear_rgba(
1678 (r as f64 / u16::MAX as f64) as f32,
1680 (g as f64 / u16::MAX as f64) as f32,
1681 (b as f64 / u16::MAX as f64) as f32,
1682 (a as f64 / u16::MAX as f64) as f32,
1683 ))
1684 }
1685 TextureFormat::Rgba32Uint => {
1686 let (r, g, b, a) = (
1687 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1688 u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1689 u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1690 u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1691 );
1692 Ok(Color::linear_rgba(
1693 (r as f64 / u32::MAX as f64) as f32,
1695 (g as f64 / u32::MAX as f64) as f32,
1696 (b as f64 / u32::MAX as f64) as f32,
1697 (a as f64 / u32::MAX as f64) as f32,
1698 ))
1699 }
1700 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1703 let x = bytes[0] as f32 / u8::MAX as f32;
1704 Ok(Color::linear_rgb(x, x, x))
1705 }
1706 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1707 let x = u16::from_le_bytes([bytes[0], bytes[1]]);
1708 let x = (x as f64 / u16::MAX as f64) as f32;
1710 Ok(Color::linear_rgb(x, x, x))
1711 }
1712 TextureFormat::R32Uint => {
1713 let x = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1714 let x = (x as f64 / u32::MAX as f64) as f32;
1716 Ok(Color::linear_rgb(x, x, x))
1717 }
1718 TextureFormat::R16Float => {
1719 let x = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1720 Ok(Color::linear_rgb(x, x, x))
1721 }
1722 TextureFormat::R32Float => {
1723 let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1724 Ok(Color::linear_rgb(x, x, x))
1725 }
1726 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1727 let r = bytes[0] as f32 / u8::MAX as f32;
1728 let g = bytes[1] as f32 / u8::MAX as f32;
1729 Ok(Color::linear_rgb(r, g, 0.0))
1730 }
1731 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1732 let r = u16::from_le_bytes([bytes[0], bytes[1]]);
1733 let g = u16::from_le_bytes([bytes[2], bytes[3]]);
1734 let r = (r as f64 / u16::MAX as f64) as f32;
1736 let g = (g as f64 / u16::MAX as f64) as f32;
1737 Ok(Color::linear_rgb(r, g, 0.0))
1738 }
1739 TextureFormat::Rg32Uint => {
1740 let r = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1741 let g = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1742 let r = (r as f64 / u32::MAX as f64) as f32;
1744 let g = (g as f64 / u32::MAX as f64) as f32;
1745 Ok(Color::linear_rgb(r, g, 0.0))
1746 }
1747 TextureFormat::Rg16Float => {
1748 let r = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1749 let g = half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32();
1750 Ok(Color::linear_rgb(r, g, 0.0))
1751 }
1752 TextureFormat::Rg32Float => {
1753 let r = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1754 let g = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1755 Ok(Color::linear_rgb(r, g, 0.0))
1756 }
1757 _ => Err(TextureAccessError::UnsupportedTextureFormat(
1758 self.texture_descriptor.format,
1759 )),
1760 }
1761 }
1762
1763 #[inline(always)]
1764 fn set_color_at_internal(
1765 &mut self,
1766 coords: UVec3,
1767 color: Color,
1768 ) -> Result<(), TextureAccessError> {
1769 let format = self.texture_descriptor.format;
1770
1771 let Some(bytes) = self.pixel_bytes_mut(coords) else {
1772 return Err(TextureAccessError::OutOfBounds {
1773 x: coords.x,
1774 y: coords.y,
1775 z: coords.z,
1776 });
1777 };
1778
1779 match format {
1782 TextureFormat::Rgba8UnormSrgb => {
1783 let [r, g, b, a] = Srgba::from(color).to_f32_array();
1784 bytes[0] = (r * u8::MAX as f32) as u8;
1785 bytes[1] = (g * u8::MAX as f32) as u8;
1786 bytes[2] = (b * u8::MAX as f32) as u8;
1787 bytes[3] = (a * u8::MAX as f32) as u8;
1788 }
1789 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => {
1790 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1791 bytes[0] = (r * u8::MAX as f32) as u8;
1792 bytes[1] = (g * u8::MAX as f32) as u8;
1793 bytes[2] = (b * u8::MAX as f32) as u8;
1794 bytes[3] = (a * u8::MAX as f32) as u8;
1795 }
1796 TextureFormat::Bgra8UnormSrgb => {
1797 let [r, g, b, a] = Srgba::from(color).to_f32_array();
1798 bytes[0] = (b * u8::MAX as f32) as u8;
1799 bytes[1] = (g * u8::MAX as f32) as u8;
1800 bytes[2] = (r * u8::MAX as f32) as u8;
1801 bytes[3] = (a * u8::MAX as f32) as u8;
1802 }
1803 TextureFormat::Bgra8Unorm => {
1804 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1805 bytes[0] = (b * u8::MAX as f32) as u8;
1806 bytes[1] = (g * u8::MAX as f32) as u8;
1807 bytes[2] = (r * u8::MAX as f32) as u8;
1808 bytes[3] = (a * u8::MAX as f32) as u8;
1809 }
1810 TextureFormat::Rgba16Float => {
1811 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1812 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1813 bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1814 bytes[4..6].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(b)));
1815 bytes[6..8].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(a)));
1816 }
1817 TextureFormat::Rgba32Float => {
1818 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1819 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1820 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1821 bytes[8..12].copy_from_slice(&f32::to_le_bytes(b));
1822 bytes[12..16].copy_from_slice(&f32::to_le_bytes(a));
1823 }
1824 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1825 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1826 let [r, g, b, a] = [
1827 (r * u16::MAX as f32) as u16,
1828 (g * u16::MAX as f32) as u16,
1829 (b * u16::MAX as f32) as u16,
1830 (a * u16::MAX as f32) as u16,
1831 ];
1832 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1833 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1834 bytes[4..6].copy_from_slice(&u16::to_le_bytes(b));
1835 bytes[6..8].copy_from_slice(&u16::to_le_bytes(a));
1836 }
1837 TextureFormat::Rgba32Uint => {
1838 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1839 let [r, g, b, a] = [
1840 (r * u32::MAX as f32) as u32,
1841 (g * u32::MAX as f32) as u32,
1842 (b * u32::MAX as f32) as u32,
1843 (a * u32::MAX as f32) as u32,
1844 ];
1845 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1846 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1847 bytes[8..12].copy_from_slice(&u32::to_le_bytes(b));
1848 bytes[12..16].copy_from_slice(&u32::to_le_bytes(a));
1849 }
1850 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1851 let linear = LinearRgba::from(color);
1853 let luminance = Xyza::from(linear).y;
1854 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1855 bytes[0] = (r * u8::MAX as f32) as u8;
1856 }
1857 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1858 let linear = LinearRgba::from(color);
1860 let luminance = Xyza::from(linear).y;
1861 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1862 let r = (r * u16::MAX as f32) as u16;
1863 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1864 }
1865 TextureFormat::R32Uint => {
1866 let linear = LinearRgba::from(color);
1868 let luminance = Xyza::from(linear).y;
1869 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1870 let r = (r as f64 * u32::MAX as f64) as u32;
1872 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1873 }
1874 TextureFormat::R16Float => {
1875 let linear = LinearRgba::from(color);
1877 let luminance = Xyza::from(linear).y;
1878 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1879 let x = half::f16::from_f32(r);
1880 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(x));
1881 }
1882 TextureFormat::R32Float => {
1883 let linear = LinearRgba::from(color);
1885 let luminance = Xyza::from(linear).y;
1886 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1887 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1888 }
1889 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1890 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1891 bytes[0] = (r * u8::MAX as f32) as u8;
1892 bytes[1] = (g * u8::MAX as f32) as u8;
1893 }
1894 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1895 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1896 let r = (r * u16::MAX as f32) as u16;
1897 let g = (g * u16::MAX as f32) as u16;
1898 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1899 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1900 }
1901 TextureFormat::Rg32Uint => {
1902 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1903 let r = (r as f64 * u32::MAX as f64) as u32;
1905 let g = (g as f64 * u32::MAX as f64) as u32;
1906 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1907 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1908 }
1909 TextureFormat::Rg16Float => {
1910 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1911 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1912 bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1913 }
1914 TextureFormat::Rg32Float => {
1915 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1916 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1917 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1918 }
1919 _ => {
1920 return Err(TextureAccessError::UnsupportedTextureFormat(
1921 self.texture_descriptor.format,
1922 ));
1923 }
1924 }
1925 Ok(())
1926 }
1927}
1928
1929#[derive(Clone, Copy, Debug)]
1930pub enum DataFormat {
1931 Rgb,
1932 Rgba,
1933 Rrr,
1934 Rrrg,
1935 Rg,
1936}
1937
1938#[derive(Clone, Copy, Debug)]
1940pub enum TranscodeFormat {
1941 Etc1s,
1942 Uastc(DataFormat),
1943 R8UnormSrgb,
1945 Rg8UnormSrgb,
1947 Rgb8,
1949}
1950
1951#[derive(Error, Debug)]
1953pub enum TextureReinterpretationError {
1954 #[error("incompatible sizes: old = {old:?} new = {new:?}")]
1955 IncompatibleSizes { old: Extent3d, new: Extent3d },
1956 #[error("must be a 2d image")]
1957 WrongDimension,
1958 #[error("must not already be a layered image")]
1959 InvalidLayerCount,
1960 #[error("can not evenly divide height = {height} by layers = {layers}")]
1961 HeightNotDivisibleByLayers { height: u32, layers: u32 },
1962}
1963
1964#[derive(Error, Debug)]
1966pub enum TextureAccessError {
1967 #[error("out of bounds (x: {x}, y: {y}, z: {z})")]
1968 OutOfBounds { x: u32, y: u32, z: u32 },
1969 #[error("unsupported texture format: {0:?}")]
1970 UnsupportedTextureFormat(TextureFormat),
1971 #[error("attempt to access texture with different dimension")]
1972 WrongDimension,
1973}
1974
1975#[derive(Error, Debug)]
1977pub enum TextureError {
1978 #[error("invalid image mime type: {0}")]
1980 InvalidImageMimeType(String),
1981 #[error("invalid image extension: {0}")]
1983 InvalidImageExtension(String),
1984 #[error("failed to load an image: {0}")]
1986 ImageError(#[from] image::ImageError),
1987 #[error("unsupported texture format: {0}")]
1989 UnsupportedTextureFormat(String),
1990 #[error("supercompression not supported: {0}")]
1992 SuperCompressionNotSupported(String),
1993 #[error("failed to decompress an image: {0}")]
1995 SuperDecompressionError(String),
1996 #[error("invalid data: {0}")]
1998 InvalidData(String),
1999 #[error("transcode error: {0}")]
2001 TranscodeError(String),
2002 #[error("format requires transcoding: {0:?}")]
2004 FormatRequiresTranscodingError(TranscodeFormat),
2005 #[error("only cubemaps with six faces are supported")]
2007 IncompleteCubemap,
2008}
2009
2010#[derive(Debug)]
2012pub enum ImageType<'a> {
2013 MimeType(&'a str),
2015 Extension(&'a str),
2017 Format(ImageFormat),
2019}
2020
2021impl<'a> ImageType<'a> {
2022 pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
2023 match self {
2024 ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
2025 .ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
2026 ImageType::Extension(extension) => ImageFormat::from_extension(extension)
2027 .ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
2028 ImageType::Format(format) => Ok(*format),
2029 }
2030 }
2031}
2032
2033pub trait Volume {
2035 fn volume(&self) -> usize;
2036}
2037
2038impl Volume for Extent3d {
2039 fn volume(&self) -> usize {
2041 (self.width * self.height * self.depth_or_array_layers) as usize
2042 }
2043}
2044
2045pub trait TextureFormatPixelInfo {
2047 fn pixel_size(&self) -> Result<usize, TextureAccessError>;
2050}
2051
2052impl TextureFormatPixelInfo for TextureFormat {
2053 fn pixel_size(&self) -> Result<usize, TextureAccessError> {
2054 let info = self;
2055 match info.block_dimensions() {
2056 (1, 1) => Ok(info.block_copy_size(None).unwrap() as usize),
2057 _ => Err(TextureAccessError::UnsupportedTextureFormat(*self)),
2058 }
2059 }
2060}
2061
2062bitflags::bitflags! {
2063 #[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
2064 #[repr(transparent)]
2065 pub struct CompressedImageFormats: u32 {
2066 const NONE = 0;
2067 const ASTC_LDR = 1 << 0;
2068 const BC = 1 << 1;
2069 const ETC2 = 1 << 2;
2070 }
2071}
2072
2073impl CompressedImageFormats {
2074 pub fn from_features(features: Features) -> Self {
2075 let mut supported_compressed_formats = Self::default();
2076 if features.contains(Features::TEXTURE_COMPRESSION_ASTC) {
2077 supported_compressed_formats |= Self::ASTC_LDR;
2078 }
2079 if features.contains(Features::TEXTURE_COMPRESSION_BC) {
2080 supported_compressed_formats |= Self::BC;
2081 }
2082 if features.contains(Features::TEXTURE_COMPRESSION_ETC2) {
2083 supported_compressed_formats |= Self::ETC2;
2084 }
2085 supported_compressed_formats
2086 }
2087
2088 pub fn supports(&self, format: TextureFormat) -> bool {
2089 match format {
2090 TextureFormat::Bc1RgbaUnorm
2091 | TextureFormat::Bc1RgbaUnormSrgb
2092 | TextureFormat::Bc2RgbaUnorm
2093 | TextureFormat::Bc2RgbaUnormSrgb
2094 | TextureFormat::Bc3RgbaUnorm
2095 | TextureFormat::Bc3RgbaUnormSrgb
2096 | TextureFormat::Bc4RUnorm
2097 | TextureFormat::Bc4RSnorm
2098 | TextureFormat::Bc5RgUnorm
2099 | TextureFormat::Bc5RgSnorm
2100 | TextureFormat::Bc6hRgbUfloat
2101 | TextureFormat::Bc6hRgbFloat
2102 | TextureFormat::Bc7RgbaUnorm
2103 | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
2104 TextureFormat::Etc2Rgb8Unorm
2105 | TextureFormat::Etc2Rgb8UnormSrgb
2106 | TextureFormat::Etc2Rgb8A1Unorm
2107 | TextureFormat::Etc2Rgb8A1UnormSrgb
2108 | TextureFormat::Etc2Rgba8Unorm
2109 | TextureFormat::Etc2Rgba8UnormSrgb
2110 | TextureFormat::EacR11Unorm
2111 | TextureFormat::EacR11Snorm
2112 | TextureFormat::EacRg11Unorm
2113 | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
2114 TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
2115 _ => true,
2116 }
2117 }
2118}
2119
2120#[derive(Resource)]
2124pub struct CompressedImageFormatSupport(pub CompressedImageFormats);
2125
2126#[cfg(test)]
2127mod test {
2128 use super::*;
2129
2130 #[test]
2131 fn image_size() {
2132 let size = Extent3d {
2133 width: 200,
2134 height: 100,
2135 depth_or_array_layers: 1,
2136 };
2137 let image = Image::new_fill(
2138 size,
2139 TextureDimension::D2,
2140 &[0, 0, 0, 255],
2141 TextureFormat::Rgba8Unorm,
2142 RenderAssetUsages::MAIN_WORLD,
2143 );
2144 assert_eq!(
2145 Vec2::new(size.width as f32, size.height as f32),
2146 image.size_f32()
2147 );
2148 }
2149
2150 #[test]
2151 fn image_default_size() {
2152 let image = Image::default();
2153 assert_eq!(UVec2::ONE, image.size());
2154 assert_eq!(Vec2::ONE, image.size_f32());
2155 }
2156
2157 #[test]
2158 fn on_edge_pixel_is_invalid() {
2159 let image = Image::new_fill(
2160 Extent3d {
2161 width: 5,
2162 height: 10,
2163 depth_or_array_layers: 1,
2164 },
2165 TextureDimension::D2,
2166 &[0, 0, 0, 255],
2167 TextureFormat::Rgba8Unorm,
2168 RenderAssetUsages::MAIN_WORLD,
2169 );
2170 assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK)));
2171 assert!(matches!(
2172 image.get_color_at(0, 10),
2173 Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 })
2174 ));
2175 assert!(matches!(
2176 image.get_color_at(5, 10),
2177 Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 })
2178 ));
2179 }
2180
2181 #[test]
2182 fn get_set_pixel_2d_with_layers() {
2183 let mut image = Image::new_fill(
2184 Extent3d {
2185 width: 5,
2186 height: 10,
2187 depth_or_array_layers: 3,
2188 },
2189 TextureDimension::D2,
2190 &[0, 0, 0, 255],
2191 TextureFormat::Rgba8Unorm,
2192 RenderAssetUsages::MAIN_WORLD,
2193 );
2194 image.set_color_at_3d(0, 0, 0, Color::WHITE).unwrap();
2195 assert!(matches!(image.get_color_at_3d(0, 0, 0), Ok(Color::WHITE)));
2196 image.set_color_at_3d(2, 3, 1, Color::WHITE).unwrap();
2197 assert!(matches!(image.get_color_at_3d(2, 3, 1), Ok(Color::WHITE)));
2198 image.set_color_at_3d(4, 9, 2, Color::WHITE).unwrap();
2199 assert!(matches!(image.get_color_at_3d(4, 9, 2), Ok(Color::WHITE)));
2200 }
2201
2202 #[test]
2203 fn resize_in_place_2d_grow_and_shrink() {
2204 use bevy_color::ColorToPacked;
2205
2206 const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2207 const GROW_FILL: LinearRgba = LinearRgba::NONE;
2208
2209 let mut image = Image::new_fill(
2210 Extent3d {
2211 width: 2,
2212 height: 2,
2213 depth_or_array_layers: 1,
2214 },
2215 TextureDimension::D2,
2216 &INITIAL_FILL.to_u8_array(),
2217 TextureFormat::Rgba8Unorm,
2218 RenderAssetUsages::MAIN_WORLD,
2219 );
2220
2221 const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2224 (0, 1, LinearRgba::RED),
2225 (1, 1, LinearRgba::GREEN),
2226 (1, 0, LinearRgba::BLUE),
2227 ];
2228
2229 for (x, y, color) in &TEST_PIXELS {
2230 image.set_color_at(*x, *y, Color::from(*color)).unwrap();
2231 }
2232
2233 image.resize_in_place(Extent3d {
2235 width: 4,
2236 height: 4,
2237 depth_or_array_layers: 1,
2238 });
2239
2240 assert!(matches!(
2242 image.get_color_at(0, 0),
2243 Ok(Color::LinearRgba(INITIAL_FILL))
2244 ));
2245 for (x, y, color) in &TEST_PIXELS {
2246 assert_eq!(
2247 image.get_color_at(*x, *y).unwrap(),
2248 Color::LinearRgba(*color)
2249 );
2250 }
2251
2252 assert!(matches!(
2254 image.get_color_at(3, 3),
2255 Ok(Color::LinearRgba(GROW_FILL))
2256 ));
2257
2258 image.resize_in_place(Extent3d {
2260 width: 1,
2261 height: 1,
2262 depth_or_array_layers: 1,
2263 });
2264
2265 assert!(image.get_color_at(1, 1).is_err());
2267 }
2268
2269 #[test]
2270 fn resize_in_place_array_grow_and_shrink() {
2271 use bevy_color::ColorToPacked;
2272
2273 const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2274 const GROW_FILL: LinearRgba = LinearRgba::NONE;
2275 const LAYERS: u32 = 4;
2276
2277 let mut image = Image::new_fill(
2278 Extent3d {
2279 width: 2,
2280 height: 2,
2281 depth_or_array_layers: LAYERS,
2282 },
2283 TextureDimension::D2,
2284 &INITIAL_FILL.to_u8_array(),
2285 TextureFormat::Rgba8Unorm,
2286 RenderAssetUsages::MAIN_WORLD,
2287 );
2288
2289 const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2292 (0, 1, LinearRgba::RED),
2293 (1, 1, LinearRgba::GREEN),
2294 (1, 0, LinearRgba::BLUE),
2295 ];
2296
2297 for z in 0..LAYERS {
2298 for (x, y, color) in &TEST_PIXELS {
2299 image
2300 .set_color_at_3d(*x, *y, z, Color::from(*color))
2301 .unwrap();
2302 }
2303 }
2304
2305 image.resize_in_place(Extent3d {
2307 width: 4,
2308 height: 4,
2309 depth_or_array_layers: LAYERS + 1,
2310 });
2311
2312 assert!(matches!(
2314 image.get_color_at(0, 0),
2315 Ok(Color::LinearRgba(INITIAL_FILL))
2316 ));
2317 for z in 0..LAYERS {
2318 for (x, y, color) in &TEST_PIXELS {
2319 assert_eq!(
2320 image.get_color_at_3d(*x, *y, z).unwrap(),
2321 Color::LinearRgba(*color)
2322 );
2323 }
2324 }
2325
2326 for z in 0..(LAYERS + 1) {
2328 assert!(matches!(
2329 image.get_color_at_3d(3, 3, z),
2330 Ok(Color::LinearRgba(GROW_FILL))
2331 ));
2332 }
2333
2334 image.resize_in_place(Extent3d {
2336 width: 1,
2337 height: 1,
2338 depth_or_array_layers: 1,
2339 });
2340
2341 assert!(image.get_color_at_3d(1, 1, 0).is_err());
2343
2344 assert!(image.get_color_at_3d(0, 0, 1).is_err());
2346
2347 image.resize_in_place(Extent3d {
2349 width: 1,
2350 height: 1,
2351 depth_or_array_layers: 2,
2352 });
2353
2354 assert!(matches!(
2356 image.get_color_at_3d(0, 0, 1),
2357 Ok(Color::LinearRgba(GROW_FILL))
2358 ));
2359 }
2360
2361 #[test]
2362 fn image_clear() {
2363 let mut image = Image::new_fill(
2364 Extent3d {
2365 width: 32,
2366 height: 32,
2367 depth_or_array_layers: 1,
2368 },
2369 TextureDimension::D2,
2370 &[0; 4],
2371 TextureFormat::Rgba8Snorm,
2372 RenderAssetUsages::all(),
2373 );
2374
2375 assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 0));
2376
2377 image.clear(&[255; 4]);
2378
2379 assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 255));
2380 }
2381
2382 #[test]
2383 fn get_or_init_sampler_modifications() {
2384 let mut default_sampler = ImageSampler::Default;
2386 let my_sampler_in_a_loader = default_sampler
2388 .get_or_init_descriptor()
2389 .set_filter(ImageFilterMode::Linear)
2390 .set_address_mode(ImageAddressMode::Repeat);
2391
2392 assert_eq!(
2393 my_sampler_in_a_loader.address_mode_u,
2394 ImageAddressMode::Repeat
2395 );
2396 assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2397 }
2398
2399 #[test]
2400 fn get_or_init_sampler_anisotropy() {
2401 let mut default_sampler = ImageSampler::Default;
2403 let my_sampler_in_a_loader = default_sampler
2405 .get_or_init_descriptor()
2406 .set_anisotropic_filter(8);
2407
2408 assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2409 assert_eq!(my_sampler_in_a_loader.anisotropy_clamp, 8);
2410 }
2411}