1use crate::ImageLoader;
2
3#[cfg(feature = "basis-universal")]
4use super::basis::*;
5#[cfg(feature = "dds")]
6use super::dds::*;
7#[cfg(feature = "ktx2")]
8use super::ktx2::*;
9use bevy_app::{App, Plugin};
10#[cfg(not(feature = "bevy_reflect"))]
11use bevy_reflect::TypePath;
12#[cfg(feature = "bevy_reflect")]
13use bevy_reflect::{std_traits::ReflectDefault, Reflect};
14#[cfg(feature = "serialize")]
15use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
16
17use bevy_asset::{uuid_handle, Asset, AssetApp, Assets, Handle, RenderAssetUsages};
18use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza};
19use bevy_ecs::resource::Resource;
20use bevy_math::{AspectRatio, UVec2, UVec3, Vec2};
21use core::hash::Hash;
22use serde::{Deserialize, Serialize};
23use thiserror::Error;
24use wgpu_types::{
25 AddressMode, CompareFunction, Extent3d, Features, FilterMode, MipmapFilterMode,
26 SamplerBorderColor, SamplerDescriptor, TextureDataOrder, TextureDescriptor, TextureDimension,
27 TextureFormat, TextureUsages, TextureViewDescriptor,
28};
29
30#[deprecated(
33 note = "Use ExtractedView::texture_format where possible. Bevy does not encourage a default TextureFormat anymore. If you really need this, use TextureFormat::Rgba8UnormSrgb"
34)]
35pub trait BevyDefault {
36 fn bevy_default() -> Self;
38}
39
40#[expect(deprecated, reason = "deprecated")]
41impl BevyDefault for TextureFormat {
42 fn bevy_default() -> Self {
43 TextureFormat::Rgba8UnormSrgb
44 }
45}
46
47pub trait TextureSrgbViewFormats {
49 fn srgb_view_formats(&self) -> &'static [TextureFormat];
51}
52
53impl TextureSrgbViewFormats for TextureFormat {
54 fn srgb_view_formats(&self) -> &'static [TextureFormat] {
58 match self {
59 TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
60 TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb],
61 TextureFormat::Bc1RgbaUnorm => &[TextureFormat::Bc1RgbaUnormSrgb],
62 TextureFormat::Bc2RgbaUnorm => &[TextureFormat::Bc2RgbaUnormSrgb],
63 TextureFormat::Bc3RgbaUnorm => &[TextureFormat::Bc3RgbaUnormSrgb],
64 TextureFormat::Bc7RgbaUnorm => &[TextureFormat::Bc7RgbaUnormSrgb],
65 TextureFormat::Etc2Rgb8Unorm => &[TextureFormat::Etc2Rgb8UnormSrgb],
66 TextureFormat::Etc2Rgb8A1Unorm => &[TextureFormat::Etc2Rgb8A1UnormSrgb],
67 TextureFormat::Etc2Rgba8Unorm => &[TextureFormat::Etc2Rgba8UnormSrgb],
68 TextureFormat::Astc {
69 block: wgpu_types::AstcBlock::B4x4,
70 channel: wgpu_types::AstcChannel::Unorm,
71 } => &[TextureFormat::Astc {
72 block: wgpu_types::AstcBlock::B4x4,
73 channel: wgpu_types::AstcChannel::UnormSrgb,
74 }],
75 TextureFormat::Astc {
76 block: wgpu_types::AstcBlock::B5x4,
77 channel: wgpu_types::AstcChannel::Unorm,
78 } => &[TextureFormat::Astc {
79 block: wgpu_types::AstcBlock::B5x4,
80 channel: wgpu_types::AstcChannel::UnormSrgb,
81 }],
82 TextureFormat::Astc {
83 block: wgpu_types::AstcBlock::B5x5,
84 channel: wgpu_types::AstcChannel::Unorm,
85 } => &[TextureFormat::Astc {
86 block: wgpu_types::AstcBlock::B5x5,
87 channel: wgpu_types::AstcChannel::UnormSrgb,
88 }],
89 TextureFormat::Astc {
90 block: wgpu_types::AstcBlock::B6x5,
91 channel: wgpu_types::AstcChannel::Unorm,
92 } => &[TextureFormat::Astc {
93 block: wgpu_types::AstcBlock::B6x5,
94 channel: wgpu_types::AstcChannel::UnormSrgb,
95 }],
96 TextureFormat::Astc {
97 block: wgpu_types::AstcBlock::B6x6,
98 channel: wgpu_types::AstcChannel::Unorm,
99 } => &[TextureFormat::Astc {
100 block: wgpu_types::AstcBlock::B6x6,
101 channel: wgpu_types::AstcChannel::UnormSrgb,
102 }],
103 TextureFormat::Astc {
104 block: wgpu_types::AstcBlock::B8x5,
105 channel: wgpu_types::AstcChannel::Unorm,
106 } => &[TextureFormat::Astc {
107 block: wgpu_types::AstcBlock::B8x5,
108 channel: wgpu_types::AstcChannel::UnormSrgb,
109 }],
110 TextureFormat::Astc {
111 block: wgpu_types::AstcBlock::B8x6,
112 channel: wgpu_types::AstcChannel::Unorm,
113 } => &[TextureFormat::Astc {
114 block: wgpu_types::AstcBlock::B8x6,
115 channel: wgpu_types::AstcChannel::UnormSrgb,
116 }],
117 TextureFormat::Astc {
118 block: wgpu_types::AstcBlock::B8x8,
119 channel: wgpu_types::AstcChannel::Unorm,
120 } => &[TextureFormat::Astc {
121 block: wgpu_types::AstcBlock::B8x8,
122 channel: wgpu_types::AstcChannel::UnormSrgb,
123 }],
124 TextureFormat::Astc {
125 block: wgpu_types::AstcBlock::B10x5,
126 channel: wgpu_types::AstcChannel::Unorm,
127 } => &[TextureFormat::Astc {
128 block: wgpu_types::AstcBlock::B10x5,
129 channel: wgpu_types::AstcChannel::UnormSrgb,
130 }],
131 TextureFormat::Astc {
132 block: wgpu_types::AstcBlock::B10x6,
133 channel: wgpu_types::AstcChannel::Unorm,
134 } => &[TextureFormat::Astc {
135 block: wgpu_types::AstcBlock::B10x6,
136 channel: wgpu_types::AstcChannel::UnormSrgb,
137 }],
138 TextureFormat::Astc {
139 block: wgpu_types::AstcBlock::B10x8,
140 channel: wgpu_types::AstcChannel::Unorm,
141 } => &[TextureFormat::Astc {
142 block: wgpu_types::AstcBlock::B10x8,
143 channel: wgpu_types::AstcChannel::UnormSrgb,
144 }],
145 TextureFormat::Astc {
146 block: wgpu_types::AstcBlock::B10x10,
147 channel: wgpu_types::AstcChannel::Unorm,
148 } => &[TextureFormat::Astc {
149 block: wgpu_types::AstcBlock::B10x10,
150 channel: wgpu_types::AstcChannel::UnormSrgb,
151 }],
152 TextureFormat::Astc {
153 block: wgpu_types::AstcBlock::B12x10,
154 channel: wgpu_types::AstcChannel::Unorm,
155 } => &[TextureFormat::Astc {
156 block: wgpu_types::AstcBlock::B12x10,
157 channel: wgpu_types::AstcChannel::UnormSrgb,
158 }],
159 TextureFormat::Astc {
160 block: wgpu_types::AstcBlock::B12x12,
161 channel: wgpu_types::AstcChannel::Unorm,
162 } => &[TextureFormat::Astc {
163 block: wgpu_types::AstcBlock::B12x12,
164 channel: wgpu_types::AstcChannel::UnormSrgb,
165 }],
166 _ => &[],
167 }
168 }
169}
170
171pub const TRANSPARENT_IMAGE_HANDLE: Handle<Image> =
177 uuid_handle!("d18ad97e-a322-4981-9505-44c59a4b5e46");
178
179pub struct ImagePlugin {
181 pub default_sampler: ImageSamplerDescriptor,
183}
184
185impl Default for ImagePlugin {
186 fn default() -> Self {
187 ImagePlugin::default_linear()
188 }
189}
190
191impl ImagePlugin {
192 pub fn default_linear() -> ImagePlugin {
194 ImagePlugin {
195 default_sampler: ImageSamplerDescriptor::linear(),
196 }
197 }
198
199 pub fn default_nearest() -> ImagePlugin {
201 ImagePlugin {
202 default_sampler: ImageSamplerDescriptor::nearest(),
203 }
204 }
205}
206
207impl Plugin for ImagePlugin {
208 fn build(&self, app: &mut App) {
209 #[cfg(feature = "exr")]
210 app.init_asset_loader::<crate::ExrTextureLoader>();
211
212 #[cfg(feature = "hdr")]
213 app.init_asset_loader::<crate::HdrTextureLoader>();
214
215 app.init_asset::<Image>();
216 #[cfg(feature = "bevy_reflect")]
217 app.register_asset_reflect::<Image>();
218
219 let mut image_assets = app.world_mut().resource_mut::<Assets<Image>>();
220
221 image_assets
222 .insert(&Handle::default(), Image::default())
223 .unwrap();
224 image_assets
225 .insert(&TRANSPARENT_IMAGE_HANDLE, Image::transparent())
226 .unwrap();
227
228 #[cfg(feature = "compressed_image_saver")]
229 if let Some(processor) = app
230 .world()
231 .get_resource::<bevy_asset::processor::AssetProcessor>()
232 {
233 processor.register_processor::<bevy_asset::processor::LoadTransformAndSave<
234 ImageLoader,
235 bevy_asset::transformer::IdentityAssetTransformer<Image>,
236 crate::CompressedImageSaver,
237 >>(crate::CompressedImageSaver.into());
238 processor.set_default_processor::<bevy_asset::processor::LoadTransformAndSave<
239 ImageLoader,
240 bevy_asset::transformer::IdentityAssetTransformer<Image>,
241 crate::CompressedImageSaver,
242 >>("png");
243 }
244
245 app.preregister_asset_loader::<ImageLoader>(ImageLoader::SUPPORTED_FILE_EXTENSIONS);
246 }
247}
248
249#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
251pub enum ImageFormat {
252 #[cfg(feature = "basis-universal")]
254 Basis,
255 #[cfg(feature = "bmp")]
257 Bmp,
258 #[cfg(feature = "dds")]
260 Dds,
261 #[cfg(feature = "ff")]
263 Farbfeld,
264 #[cfg(feature = "gif")]
266 Gif,
267 #[cfg(feature = "exr")]
269 OpenExr,
270 #[cfg(feature = "hdr")]
272 Hdr,
273 #[cfg(feature = "ico")]
275 Ico,
276 #[cfg(feature = "jpeg")]
278 Jpeg,
279 #[cfg(feature = "ktx2")]
281 Ktx2,
282 #[cfg(feature = "png")]
284 Png,
285 #[cfg(feature = "pnm")]
287 Pnm,
288 #[cfg(feature = "qoi")]
290 Qoi,
291 #[cfg(feature = "tga")]
293 Tga,
294 #[cfg(feature = "tiff")]
296 Tiff,
297 #[cfg(feature = "webp")]
299 WebP,
300}
301
302macro_rules! feature_gate {
303 ($feature: tt, $value: ident) => {{
304 #[cfg(not(feature = $feature))]
305 {
306 tracing::warn!("feature \"{}\" is not enabled", $feature);
307 return None;
308 }
309 #[cfg(feature = $feature)]
310 ImageFormat::$value
311 }};
312}
313
314impl ImageFormat {
315 pub const fn to_file_extensions(&self) -> &'static [&'static str] {
317 match self {
318 #[cfg(feature = "basis-universal")]
319 ImageFormat::Basis => &["basis"],
320 #[cfg(feature = "bmp")]
321 ImageFormat::Bmp => &["bmp"],
322 #[cfg(feature = "dds")]
323 ImageFormat::Dds => &["dds"],
324 #[cfg(feature = "ff")]
325 ImageFormat::Farbfeld => &["ff", "farbfeld"],
326 #[cfg(feature = "gif")]
327 ImageFormat::Gif => &["gif"],
328 #[cfg(feature = "exr")]
329 ImageFormat::OpenExr => &["exr"],
330 #[cfg(feature = "hdr")]
331 ImageFormat::Hdr => &["hdr"],
332 #[cfg(feature = "ico")]
333 ImageFormat::Ico => &["ico"],
334 #[cfg(feature = "jpeg")]
335 ImageFormat::Jpeg => &["jpg", "jpeg"],
336 #[cfg(feature = "ktx2")]
337 ImageFormat::Ktx2 => &["ktx2"],
338 #[cfg(feature = "pnm")]
339 ImageFormat::Pnm => &["pam", "pbm", "pgm", "ppm"],
340 #[cfg(feature = "png")]
341 ImageFormat::Png => &["png"],
342 #[cfg(feature = "qoi")]
343 ImageFormat::Qoi => &["qoi"],
344 #[cfg(feature = "tga")]
345 ImageFormat::Tga => &["tga"],
346 #[cfg(feature = "tiff")]
347 ImageFormat::Tiff => &["tif", "tiff"],
348 #[cfg(feature = "webp")]
349 ImageFormat::WebP => &["webp"],
350 #[expect(
352 clippy::allow_attributes,
353 reason = "`unreachable_patterns` may not always lint"
354 )]
355 #[allow(
356 unreachable_patterns,
357 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
358 )]
359 _ => &[],
360 }
361 }
362
363 pub const fn to_mime_types(&self) -> &'static [&'static str] {
367 match self {
368 #[cfg(feature = "basis-universal")]
369 ImageFormat::Basis => &["image/basis", "image/x-basis"],
370 #[cfg(feature = "bmp")]
371 ImageFormat::Bmp => &["image/bmp", "image/x-bmp"],
372 #[cfg(feature = "dds")]
373 ImageFormat::Dds => &["image/vnd-ms.dds"],
374 #[cfg(feature = "hdr")]
375 ImageFormat::Hdr => &["image/vnd.radiance"],
376 #[cfg(feature = "gif")]
377 ImageFormat::Gif => &["image/gif"],
378 #[cfg(feature = "ff")]
379 ImageFormat::Farbfeld => &[],
380 #[cfg(feature = "ico")]
381 ImageFormat::Ico => &["image/x-icon"],
382 #[cfg(feature = "jpeg")]
383 ImageFormat::Jpeg => &["image/jpeg"],
384 #[cfg(feature = "ktx2")]
385 ImageFormat::Ktx2 => &["image/ktx2"],
386 #[cfg(feature = "png")]
387 ImageFormat::Png => &["image/png"],
388 #[cfg(feature = "qoi")]
389 ImageFormat::Qoi => &["image/qoi", "image/x-qoi"],
390 #[cfg(feature = "exr")]
391 ImageFormat::OpenExr => &["image/x-exr"],
392 #[cfg(feature = "pnm")]
393 ImageFormat::Pnm => &[
394 "image/x-portable-bitmap",
395 "image/x-portable-graymap",
396 "image/x-portable-pixmap",
397 "image/x-portable-anymap",
398 ],
399 #[cfg(feature = "tga")]
400 ImageFormat::Tga => &["image/x-targa", "image/x-tga"],
401 #[cfg(feature = "tiff")]
402 ImageFormat::Tiff => &["image/tiff"],
403 #[cfg(feature = "webp")]
404 ImageFormat::WebP => &["image/webp"],
405 #[expect(
407 clippy::allow_attributes,
408 reason = "`unreachable_patterns` may not always lint"
409 )]
410 #[allow(
411 unreachable_patterns,
412 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
413 )]
414 _ => &[],
415 }
416 }
417
418 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
422 #[expect(
423 clippy::allow_attributes,
424 reason = "`unreachable_code` may not always lint"
425 )]
426 #[allow(
427 unreachable_code,
428 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
429 )]
430 Some(match mime_type.to_ascii_lowercase().as_str() {
431 "image/basis" | "image/x-basis" => feature_gate!("basis-universal", Basis),
433 "image/bmp" | "image/x-bmp" => feature_gate!("bmp", Bmp),
434 "image/vnd-ms.dds" => feature_gate!("dds", Dds),
435 "image/vnd.radiance" => feature_gate!("hdr", Hdr),
436 "image/gif" => feature_gate!("gif", Gif),
437 "image/x-icon" => feature_gate!("ico", Ico),
438 "image/jpeg" => feature_gate!("jpeg", Jpeg),
439 "image/ktx2" => feature_gate!("ktx2", Ktx2),
440 "image/png" => feature_gate!("png", Png),
441 "image/qoi" | "image/x-qoi" => feature_gate!("qoi", Qoi),
442 "image/x-exr" => feature_gate!("exr", OpenExr),
443 "image/x-portable-bitmap"
444 | "image/x-portable-graymap"
445 | "image/x-portable-pixmap"
446 | "image/x-portable-anymap" => feature_gate!("pnm", Pnm),
447 "image/x-targa" | "image/x-tga" => feature_gate!("tga", Tga),
448 "image/tiff" => feature_gate!("tiff", Tiff),
449 "image/webp" => feature_gate!("webp", WebP),
450 _ => return None,
451 })
452 }
453
454 pub fn from_extension(extension: &str) -> Option<Self> {
458 #[expect(
459 clippy::allow_attributes,
460 reason = "`unreachable_code` may not always lint"
461 )]
462 #[allow(
463 unreachable_code,
464 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
465 )]
466 Some(match extension.to_ascii_lowercase().as_str() {
467 "basis" => feature_gate!("basis-universal", Basis),
468 "bmp" => feature_gate!("bmp", Bmp),
469 "dds" => feature_gate!("dds", Dds),
470 "ff" | "farbfeld" => feature_gate!("ff", Farbfeld),
471 "gif" => feature_gate!("gif", Gif),
472 "exr" => feature_gate!("exr", OpenExr),
473 "hdr" => feature_gate!("hdr", Hdr),
474 "ico" => feature_gate!("ico", Ico),
475 "jpg" | "jpeg" => feature_gate!("jpeg", Jpeg),
476 "ktx2" => feature_gate!("ktx2", Ktx2),
477 "pam" | "pbm" | "pgm" | "ppm" => feature_gate!("pnm", Pnm),
478 "png" => feature_gate!("png", Png),
479 "qoi" => feature_gate!("qoi", Qoi),
480 "tga" => feature_gate!("tga", Tga),
481 "tif" | "tiff" => feature_gate!("tiff", Tiff),
482 "webp" => feature_gate!("webp", WebP),
483 _ => return None,
484 })
485 }
486
487 pub fn as_image_crate_format(&self) -> Option<image::ImageFormat> {
489 #[expect(
490 clippy::allow_attributes,
491 reason = "`unreachable_code` may not always lint"
492 )]
493 #[allow(
494 unreachable_code,
495 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
496 )]
497 Some(match self {
498 #[cfg(feature = "bmp")]
499 ImageFormat::Bmp => image::ImageFormat::Bmp,
500 #[cfg(feature = "dds")]
501 ImageFormat::Dds => image::ImageFormat::Dds,
502 #[cfg(feature = "ff")]
503 ImageFormat::Farbfeld => image::ImageFormat::Farbfeld,
504 #[cfg(feature = "gif")]
505 ImageFormat::Gif => image::ImageFormat::Gif,
506 #[cfg(feature = "exr")]
507 ImageFormat::OpenExr => image::ImageFormat::OpenExr,
508 #[cfg(feature = "hdr")]
509 ImageFormat::Hdr => image::ImageFormat::Hdr,
510 #[cfg(feature = "ico")]
511 ImageFormat::Ico => image::ImageFormat::Ico,
512 #[cfg(feature = "jpeg")]
513 ImageFormat::Jpeg => image::ImageFormat::Jpeg,
514 #[cfg(feature = "png")]
515 ImageFormat::Png => image::ImageFormat::Png,
516 #[cfg(feature = "pnm")]
517 ImageFormat::Pnm => image::ImageFormat::Pnm,
518 #[cfg(feature = "qoi")]
519 ImageFormat::Qoi => image::ImageFormat::Qoi,
520 #[cfg(feature = "tga")]
521 ImageFormat::Tga => image::ImageFormat::Tga,
522 #[cfg(feature = "tiff")]
523 ImageFormat::Tiff => image::ImageFormat::Tiff,
524 #[cfg(feature = "webp")]
525 ImageFormat::WebP => image::ImageFormat::WebP,
526 #[cfg(feature = "basis-universal")]
527 ImageFormat::Basis => return None,
528 #[cfg(feature = "ktx2")]
529 ImageFormat::Ktx2 => return None,
530 #[expect(
532 clippy::allow_attributes,
533 reason = "`unreachable_patterns` may not always lint"
534 )]
535 #[allow(
536 unreachable_patterns,
537 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
538 )]
539 _ => return None,
540 })
541 }
542
543 pub fn from_image_crate_format(format: image::ImageFormat) -> Option<ImageFormat> {
547 #[expect(
548 clippy::allow_attributes,
549 reason = "`unreachable_code` may not always lint"
550 )]
551 #[allow(
552 unreachable_code,
553 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
554 )]
555 Some(match format {
556 image::ImageFormat::Bmp => feature_gate!("bmp", Bmp),
557 image::ImageFormat::Dds => feature_gate!("dds", Dds),
558 image::ImageFormat::Farbfeld => feature_gate!("ff", Farbfeld),
559 image::ImageFormat::Gif => feature_gate!("gif", Gif),
560 image::ImageFormat::OpenExr => feature_gate!("exr", OpenExr),
561 image::ImageFormat::Hdr => feature_gate!("hdr", Hdr),
562 image::ImageFormat::Ico => feature_gate!("ico", Ico),
563 image::ImageFormat::Jpeg => feature_gate!("jpeg", Jpeg),
564 image::ImageFormat::Png => feature_gate!("png", Png),
565 image::ImageFormat::Pnm => feature_gate!("pnm", Pnm),
566 image::ImageFormat::Qoi => feature_gate!("qoi", Qoi),
567 image::ImageFormat::Tga => feature_gate!("tga", Tga),
568 image::ImageFormat::Tiff => feature_gate!("tiff", Tiff),
569 image::ImageFormat::WebP => feature_gate!("webp", WebP),
570 _ => return None,
571 })
572 }
573}
574
575pub trait ToExtents {
577 fn to_extents(self) -> Extent3d;
579}
580impl ToExtents for UVec2 {
581 fn to_extents(self) -> Extent3d {
582 Extent3d {
583 width: self.x,
584 height: self.y,
585 depth_or_array_layers: 1,
586 }
587 }
588}
589impl ToExtents for UVec3 {
590 fn to_extents(self) -> Extent3d {
591 Extent3d {
592 width: self.x,
593 height: self.y,
594 depth_or_array_layers: self.z,
595 }
596 }
597}
598
599#[derive(Asset, Debug, Clone, PartialEq)]
606#[cfg_attr(
607 feature = "bevy_reflect",
608 derive(Reflect),
609 reflect(opaque, Default, Debug, Clone)
610)]
611#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
612#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))]
613pub struct Image {
614 pub data: Option<Vec<u8>>,
619 pub data_order: TextureDataOrder,
623 pub texture_descriptor: TextureDescriptor<Option<&'static str>, &'static [TextureFormat]>,
632 pub sampler: ImageSampler,
634 pub texture_view_descriptor: Option<TextureViewDescriptor<Option<&'static str>>>,
642 pub asset_usage: RenderAssetUsages,
644 pub copy_on_resize: bool,
646}
647
648#[cfg(feature = "serialize")]
649mod image_serde {
650 use super::*;
651
652 impl Serialize for Image {
653 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
654 super::super::serialized_image::SerializedImage::from_image(self.clone())
655 .serialize(serializer)
656 }
657 }
658
659 impl<'de> Deserialize<'de> for Image {
660 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
661 super::super::serialized_image::SerializedImage::deserialize(deserializer)
662 .map(super::super::serialized_image::SerializedImage::into_image)
663 }
664 }
665}
666
667#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
671#[cfg_attr(
672 feature = "bevy_reflect",
673 derive(Reflect),
674 reflect(Default, Debug, Clone)
675)]
676pub enum ImageSampler {
677 #[default]
679 Default,
680 Descriptor(ImageSamplerDescriptor),
682}
683
684impl ImageSampler {
685 #[inline]
687 pub fn linear() -> ImageSampler {
688 ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
689 }
690
691 #[inline]
693 pub fn nearest() -> ImageSampler {
694 ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
695 }
696
697 pub fn get_or_init_descriptor(&mut self) -> &mut ImageSamplerDescriptor {
702 match self {
703 ImageSampler::Default => {
704 *self = ImageSampler::Descriptor(ImageSamplerDescriptor::default());
705 match self {
706 ImageSampler::Descriptor(descriptor) => descriptor,
707 _ => unreachable!(),
708 }
709 }
710 ImageSampler::Descriptor(descriptor) => descriptor,
711 }
712 }
713}
714
715#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
721#[cfg_attr(
722 feature = "bevy_reflect",
723 derive(Reflect),
724 reflect(Default, Debug, Clone)
725)]
726pub enum ImageAddressMode {
727 #[default]
732 ClampToEdge,
733 Repeat,
738 MirrorRepeat,
743 ClampToBorder,
749}
750
751#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
755#[cfg_attr(
756 feature = "bevy_reflect",
757 derive(Reflect),
758 reflect(Default, Debug, Clone)
759)]
760pub enum ImageFilterMode {
761 #[default]
765 Nearest,
766 Linear,
770}
771
772#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
776#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
777pub enum ImageCompareFunction {
778 Never,
780 Less,
782 Equal,
786 LessEqual,
788 Greater,
790 NotEqual,
794 GreaterEqual,
796 Always,
798}
799
800#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
804#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
805pub enum ImageSamplerBorderColor {
806 TransparentBlack,
808 OpaqueBlack,
810 OpaqueWhite,
812 Zero,
818}
819
820#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
828#[cfg_attr(
829 feature = "bevy_reflect",
830 derive(Reflect),
831 reflect(Default, Debug, Clone)
832)]
833pub struct ImageSamplerDescriptor {
834 pub label: Option<String>,
836 pub address_mode_u: ImageAddressMode,
838 pub address_mode_v: ImageAddressMode,
840 pub address_mode_w: ImageAddressMode,
842 pub mag_filter: ImageFilterMode,
844 pub min_filter: ImageFilterMode,
846 pub mipmap_filter: ImageFilterMode,
848 pub lod_min_clamp: f32,
850 pub lod_max_clamp: f32,
852 pub compare: Option<ImageCompareFunction>,
854 pub anisotropy_clamp: u16,
856 pub border_color: Option<ImageSamplerBorderColor>,
858}
859
860impl Default for ImageSamplerDescriptor {
861 fn default() -> Self {
862 Self {
863 address_mode_u: Default::default(),
864 address_mode_v: Default::default(),
865 address_mode_w: Default::default(),
866 mag_filter: Default::default(),
867 min_filter: Default::default(),
868 mipmap_filter: Default::default(),
869 lod_min_clamp: 0.0,
870 lod_max_clamp: 32.0,
871 compare: None,
872 anisotropy_clamp: 1,
873 border_color: None,
874 label: None,
875 }
876 }
877}
878
879impl ImageSamplerDescriptor {
880 #[inline]
882 pub fn linear() -> ImageSamplerDescriptor {
883 ImageSamplerDescriptor {
884 mag_filter: ImageFilterMode::Linear,
885 min_filter: ImageFilterMode::Linear,
886 mipmap_filter: ImageFilterMode::Linear,
887 ..Default::default()
888 }
889 }
890
891 #[inline]
893 pub fn nearest() -> ImageSamplerDescriptor {
894 ImageSamplerDescriptor {
895 mag_filter: ImageFilterMode::Nearest,
896 min_filter: ImageFilterMode::Nearest,
897 mipmap_filter: ImageFilterMode::Nearest,
898 ..Default::default()
899 }
900 }
901
902 #[inline]
904 pub fn set_filter(&mut self, filter: ImageFilterMode) -> &mut Self {
905 self.mag_filter = filter;
906 self.min_filter = filter;
907 self.mipmap_filter = filter;
908 self
909 }
910
911 #[inline]
913 pub fn set_address_mode(&mut self, address_mode: ImageAddressMode) -> &mut Self {
914 self.address_mode_u = address_mode;
915 self.address_mode_v = address_mode;
916 self.address_mode_w = address_mode;
917 self
918 }
919
920 #[inline]
924 pub fn set_anisotropic_filter(&mut self, anisotropy_clamp: u16) -> &mut Self {
925 self.mag_filter = ImageFilterMode::Linear;
926 self.min_filter = ImageFilterMode::Linear;
927 self.mipmap_filter = ImageFilterMode::Linear;
928 self.anisotropy_clamp = anisotropy_clamp;
929 self
930 }
931
932 pub fn as_wgpu(&self) -> SamplerDescriptor<Option<&str>> {
934 SamplerDescriptor {
935 label: self.label.as_deref(),
936 address_mode_u: self.address_mode_u.into(),
937 address_mode_v: self.address_mode_v.into(),
938 address_mode_w: self.address_mode_w.into(),
939 mag_filter: self.mag_filter.into(),
940 min_filter: self.min_filter.into(),
941 mipmap_filter: self.mipmap_filter.into(),
942 lod_min_clamp: self.lod_min_clamp,
943 lod_max_clamp: self.lod_max_clamp,
944 compare: self.compare.map(Into::into),
945 anisotropy_clamp: self.anisotropy_clamp,
946 border_color: self.border_color.map(Into::into),
947 }
948 }
949}
950
951impl From<ImageAddressMode> for AddressMode {
952 fn from(value: ImageAddressMode) -> Self {
953 match value {
954 ImageAddressMode::ClampToEdge => AddressMode::ClampToEdge,
955 ImageAddressMode::Repeat => AddressMode::Repeat,
956 ImageAddressMode::MirrorRepeat => AddressMode::MirrorRepeat,
957 ImageAddressMode::ClampToBorder => AddressMode::ClampToBorder,
958 }
959 }
960}
961
962impl From<ImageFilterMode> for FilterMode {
963 fn from(value: ImageFilterMode) -> Self {
964 match value {
965 ImageFilterMode::Nearest => FilterMode::Nearest,
966 ImageFilterMode::Linear => FilterMode::Linear,
967 }
968 }
969}
970
971impl From<ImageFilterMode> for MipmapFilterMode {
972 fn from(value: ImageFilterMode) -> Self {
973 match value {
974 ImageFilterMode::Nearest => MipmapFilterMode::Nearest,
975 ImageFilterMode::Linear => MipmapFilterMode::Linear,
976 }
977 }
978}
979
980impl From<ImageCompareFunction> for CompareFunction {
981 fn from(value: ImageCompareFunction) -> Self {
982 match value {
983 ImageCompareFunction::Never => CompareFunction::Never,
984 ImageCompareFunction::Less => CompareFunction::Less,
985 ImageCompareFunction::Equal => CompareFunction::Equal,
986 ImageCompareFunction::LessEqual => CompareFunction::LessEqual,
987 ImageCompareFunction::Greater => CompareFunction::Greater,
988 ImageCompareFunction::NotEqual => CompareFunction::NotEqual,
989 ImageCompareFunction::GreaterEqual => CompareFunction::GreaterEqual,
990 ImageCompareFunction::Always => CompareFunction::Always,
991 }
992 }
993}
994
995impl From<ImageSamplerBorderColor> for SamplerBorderColor {
996 fn from(value: ImageSamplerBorderColor) -> Self {
997 match value {
998 ImageSamplerBorderColor::TransparentBlack => SamplerBorderColor::TransparentBlack,
999 ImageSamplerBorderColor::OpaqueBlack => SamplerBorderColor::OpaqueBlack,
1000 ImageSamplerBorderColor::OpaqueWhite => SamplerBorderColor::OpaqueWhite,
1001 ImageSamplerBorderColor::Zero => SamplerBorderColor::Zero,
1002 }
1003 }
1004}
1005
1006impl From<AddressMode> for ImageAddressMode {
1007 fn from(value: AddressMode) -> Self {
1008 match value {
1009 AddressMode::ClampToEdge => ImageAddressMode::ClampToEdge,
1010 AddressMode::Repeat => ImageAddressMode::Repeat,
1011 AddressMode::MirrorRepeat => ImageAddressMode::MirrorRepeat,
1012 AddressMode::ClampToBorder => ImageAddressMode::ClampToBorder,
1013 }
1014 }
1015}
1016
1017impl From<MipmapFilterMode> for ImageFilterMode {
1018 fn from(value: MipmapFilterMode) -> Self {
1019 match value {
1020 MipmapFilterMode::Nearest => ImageFilterMode::Nearest,
1021 MipmapFilterMode::Linear => ImageFilterMode::Linear,
1022 }
1023 }
1024}
1025
1026impl From<FilterMode> for ImageFilterMode {
1027 fn from(value: FilterMode) -> Self {
1028 match value {
1029 FilterMode::Nearest => ImageFilterMode::Nearest,
1030 FilterMode::Linear => ImageFilterMode::Linear,
1031 }
1032 }
1033}
1034
1035impl From<CompareFunction> for ImageCompareFunction {
1036 fn from(value: CompareFunction) -> Self {
1037 match value {
1038 CompareFunction::Never => ImageCompareFunction::Never,
1039 CompareFunction::Less => ImageCompareFunction::Less,
1040 CompareFunction::Equal => ImageCompareFunction::Equal,
1041 CompareFunction::LessEqual => ImageCompareFunction::LessEqual,
1042 CompareFunction::Greater => ImageCompareFunction::Greater,
1043 CompareFunction::NotEqual => ImageCompareFunction::NotEqual,
1044 CompareFunction::GreaterEqual => ImageCompareFunction::GreaterEqual,
1045 CompareFunction::Always => ImageCompareFunction::Always,
1046 }
1047 }
1048}
1049
1050impl From<SamplerBorderColor> for ImageSamplerBorderColor {
1051 fn from(value: SamplerBorderColor) -> Self {
1052 match value {
1053 SamplerBorderColor::TransparentBlack => ImageSamplerBorderColor::TransparentBlack,
1054 SamplerBorderColor::OpaqueBlack => ImageSamplerBorderColor::OpaqueBlack,
1055 SamplerBorderColor::OpaqueWhite => ImageSamplerBorderColor::OpaqueWhite,
1056 SamplerBorderColor::Zero => ImageSamplerBorderColor::Zero,
1057 }
1058 }
1059}
1060
1061impl From<SamplerDescriptor<Option<&str>>> for ImageSamplerDescriptor {
1062 fn from(value: SamplerDescriptor<Option<&str>>) -> Self {
1063 ImageSamplerDescriptor {
1064 label: value.label.map(ToString::to_string),
1065 address_mode_u: value.address_mode_u.into(),
1066 address_mode_v: value.address_mode_v.into(),
1067 address_mode_w: value.address_mode_w.into(),
1068 mag_filter: value.mag_filter.into(),
1069 min_filter: value.min_filter.into(),
1070 mipmap_filter: value.mipmap_filter.into(),
1071 lod_min_clamp: value.lod_min_clamp,
1072 lod_max_clamp: value.lod_max_clamp,
1073 compare: value.compare.map(Into::into),
1074 anisotropy_clamp: value.anisotropy_clamp,
1075 border_color: value.border_color.map(Into::into),
1076 }
1077 }
1078}
1079
1080impl Default for Image {
1081 fn default() -> Self {
1083 let mut image = Image::default_uninit();
1084 image.data = Some(vec![
1085 255;
1086 image
1087 .texture_descriptor
1088 .format
1089 .pixel_size()
1090 .unwrap_or(0)
1091 ]);
1092 image
1093 }
1094}
1095
1096impl Image {
1097 pub fn new(
1103 size: Extent3d,
1104 dimension: TextureDimension,
1105 data: Vec<u8>,
1106 format: TextureFormat,
1107 asset_usage: RenderAssetUsages,
1108 ) -> Self {
1109 if let Ok(pixel_size) = format.pixel_size() {
1110 debug_assert_eq!(
1111 pixel_count(size) * pixel_size,
1112 data.len(),
1113 "Pixel data, size and format have to match",
1114 );
1115 }
1116 let mut image = Image::new_uninit(size, dimension, format, asset_usage);
1117 image.data = Some(data);
1118 image
1119 }
1120
1121 pub fn new_uninit(
1123 size: Extent3d,
1124 dimension: TextureDimension,
1125 format: TextureFormat,
1126 asset_usage: RenderAssetUsages,
1127 ) -> Self {
1128 Image {
1129 data: None,
1130 data_order: TextureDataOrder::default(),
1131 texture_descriptor: TextureDescriptor {
1132 size,
1133 format,
1134 dimension,
1135 label: None,
1136 mip_level_count: 1,
1137 sample_count: 1,
1138 usage: TextureUsages::TEXTURE_BINDING
1139 | TextureUsages::COPY_DST
1140 | TextureUsages::COPY_SRC,
1141 view_formats: &[],
1142 },
1143 sampler: ImageSampler::Default,
1144 texture_view_descriptor: None,
1145 asset_usage,
1146 copy_on_resize: false,
1147 }
1148 }
1149
1150 pub fn transparent() -> Image {
1154 let format = TextureFormat::Rgba8UnormSrgb;
1158 if let Ok(pixel_size) = format.pixel_size() {
1159 debug_assert!(pixel_size == 4);
1160 }
1161 let data = vec![255, 255, 255, 0];
1162 Image::new(
1163 Extent3d::default(),
1164 TextureDimension::D2,
1165 data,
1166 format,
1167 RenderAssetUsages::default(),
1168 )
1169 }
1170 pub fn default_uninit() -> Image {
1172 Image::new_uninit(
1173 Extent3d::default(),
1174 TextureDimension::D2,
1175 TextureFormat::Rgba8UnormSrgb,
1176 RenderAssetUsages::default(),
1177 )
1178 }
1179
1180 pub fn new_fill(
1186 size: Extent3d,
1187 dimension: TextureDimension,
1188 pixel: &[u8],
1189 format: TextureFormat,
1190 asset_usage: RenderAssetUsages,
1191 ) -> Self {
1192 let mut image = Image::new_uninit(size, dimension, format, asset_usage);
1193 if let Ok(pixel_size) = image.texture_descriptor.format.pixel_size()
1194 && pixel_size > 0
1195 {
1196 let byte_len = pixel_size * pixel_count(size);
1197 debug_assert_eq!(
1198 pixel.len() % pixel_size,
1199 0,
1200 "Must not have incomplete pixel data (pixel size is {}B).",
1201 pixel_size,
1202 );
1203 debug_assert!(
1204 pixel.len() <= byte_len,
1205 "Fill data must fit within pixel buffer (expected {byte_len}B).",
1206 );
1207 let data = pixel.iter().copied().cycle().take(byte_len).collect();
1208 image.data = Some(data);
1209 }
1210 image
1211 }
1212
1213 pub fn new_target_texture(
1233 width: u32,
1234 height: u32,
1235 format: TextureFormat,
1236 view_format: Option<TextureFormat>,
1237 ) -> Self {
1238 let size = Extent3d {
1239 width,
1240 height,
1241 ..Default::default()
1242 };
1243 let usage = TextureUsages::TEXTURE_BINDING
1245 | TextureUsages::COPY_DST
1246 | TextureUsages::RENDER_ATTACHMENT;
1247 let data = vec![
1249 0;
1250 format.pixel_size().expect(
1251 "Failed to create Image: can't get pixel size for this TextureFormat"
1252 ) * pixel_count(size)
1253 ];
1254
1255 Image {
1256 data: Some(data),
1257 data_order: TextureDataOrder::default(),
1258 texture_descriptor: TextureDescriptor {
1259 size,
1260 format,
1261 dimension: TextureDimension::D2,
1262 label: None,
1263 mip_level_count: 1,
1264 sample_count: 1,
1265 usage,
1266 view_formats: match view_format {
1267 Some(_) => format.srgb_view_formats(),
1268 None => &[],
1269 },
1270 },
1271 sampler: ImageSampler::Default,
1272 texture_view_descriptor: view_format.map(|f| TextureViewDescriptor {
1273 format: Some(f),
1274 ..Default::default()
1275 }),
1276 asset_usage: RenderAssetUsages::default(),
1277 copy_on_resize: true,
1278 }
1279 }
1280
1281 #[inline]
1283 pub fn width(&self) -> u32 {
1284 self.texture_descriptor.size.width
1285 }
1286
1287 #[inline]
1289 pub fn height(&self) -> u32 {
1290 self.texture_descriptor.size.height
1291 }
1292
1293 #[inline]
1295 pub fn aspect_ratio(&self) -> AspectRatio {
1296 AspectRatio::try_from_pixels(self.width(), self.height()).expect(
1297 "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
1298 )
1299 }
1300
1301 #[inline]
1303 pub fn size_f32(&self) -> Vec2 {
1304 Vec2::new(self.width() as f32, self.height() as f32)
1305 }
1306
1307 #[inline]
1309 pub fn size(&self) -> UVec2 {
1310 UVec2::new(self.width(), self.height())
1311 }
1312
1313 pub fn resize(&mut self, size: Extent3d) {
1318 self.texture_descriptor.size = size;
1319 if let Some(ref mut data) = self.data
1320 && let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1321 {
1322 data.resize(pixel_count(size) * pixel_size, 0);
1323 }
1324 }
1325
1326 pub fn reinterpret_size(
1329 &mut self,
1330 new_size: Extent3d,
1331 ) -> Result<(), TextureReinterpretationError> {
1332 if pixel_count(new_size) != pixel_count(self.texture_descriptor.size) {
1333 return Err(TextureReinterpretationError::IncompatibleSizes {
1334 old: self.texture_descriptor.size,
1335 new: new_size,
1336 });
1337 }
1338
1339 self.texture_descriptor.size = new_size;
1340 Ok(())
1341 }
1342
1343 pub fn resize_in_place(&mut self, new_size: Extent3d) {
1348 if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size() {
1349 let old_size = self.texture_descriptor.size;
1350 let byte_len = pixel_size * pixel_count(new_size);
1351 self.texture_descriptor.size = new_size;
1352
1353 let Some(ref mut data) = self.data else {
1354 self.copy_on_resize = true;
1355 return;
1356 };
1357
1358 let mut new: Vec<u8> = vec![0; byte_len];
1359
1360 let copy_width = old_size.width.min(new_size.width) as usize;
1361 let copy_height = old_size.height.min(new_size.height) as usize;
1362 let copy_depth = old_size
1363 .depth_or_array_layers
1364 .min(new_size.depth_or_array_layers) as usize;
1365
1366 let old_row_stride = old_size.width as usize * pixel_size;
1367 let old_layer_stride = old_size.height as usize * old_row_stride;
1368
1369 let new_row_stride = new_size.width as usize * pixel_size;
1370 let new_layer_stride = new_size.height as usize * new_row_stride;
1371
1372 for z in 0..copy_depth {
1373 for y in 0..copy_height {
1374 let old_offset = z * old_layer_stride + y * old_row_stride;
1375 let new_offset = z * new_layer_stride + y * new_row_stride;
1376
1377 let old_range = (old_offset)..(old_offset + copy_width * pixel_size);
1378 let new_range = (new_offset)..(new_offset + copy_width * pixel_size);
1379
1380 new[new_range].copy_from_slice(&data[old_range]);
1381 }
1382 }
1383
1384 self.data = Some(new);
1385 }
1386 }
1387
1388 pub fn reinterpret_stacked_2d_as_array(
1396 &mut self,
1397 layers: u32,
1398 ) -> Result<(), TextureReinterpretationError> {
1399 if layers < 2 {
1401 return Err(TextureReinterpretationError::NotEnoughLayers);
1402 }
1403 if self.texture_descriptor.dimension != TextureDimension::D2 {
1404 return Err(TextureReinterpretationError::WrongDimension);
1405 }
1406 if self.texture_descriptor.size.depth_or_array_layers != 1 {
1407 return Err(TextureReinterpretationError::InvalidLayerCount);
1408 }
1409 if !self.height().is_multiple_of(layers) {
1410 return Err(TextureReinterpretationError::HeightNotDivisibleByLayers {
1411 height: self.height(),
1412 layers,
1413 });
1414 }
1415
1416 self.reinterpret_size(Extent3d {
1417 width: self.width(),
1418 height: self.height() / layers,
1419 depth_or_array_layers: layers,
1420 })?;
1421
1422 Ok(())
1423 }
1424
1425 pub fn create_stacked_array_from_2d_grid(
1434 &self,
1435 rows: u32,
1436 columns: u32,
1437 ) -> Result<Image, TextureReinterpretationError> {
1438 if rows * columns < 2 {
1441 return Err(TextureReinterpretationError::NotEnoughLayers);
1442 }
1443 if self.texture_descriptor.dimension != TextureDimension::D2 {
1445 return Err(TextureReinterpretationError::WrongDimension);
1446 }
1447 if self.texture_descriptor.size.depth_or_array_layers != 1 {
1448 return Err(TextureReinterpretationError::InvalidLayerCount);
1449 }
1450 if !self.height().is_multiple_of(rows) {
1451 return Err(
1452 TextureReinterpretationError::GridHeightNotDivisibleByTileHeight {
1453 height: self.height(),
1454 tile_count_y: rows,
1455 },
1456 );
1457 }
1458 if !self.width().is_multiple_of(columns) {
1459 return Err(
1460 TextureReinterpretationError::GridWidthNotDivisibleByTileWidth {
1461 width: self.width(),
1462 tile_count_x: columns,
1463 },
1464 );
1465 }
1466
1467 let tile_width = self.width() / columns;
1468 let tile_height = self.height() / rows;
1469 let tiles_x = columns as usize;
1470 let tiles_y = rows as usize;
1471 let image_width = self.width() as usize;
1472 let total_tiles = tiles_x * tiles_y;
1473
1474 let new_data = match &self.data {
1475 Some(pixels) => {
1476 let mut new_data: Vec<u8> = Vec::with_capacity(pixels.len());
1477
1478 let pixel_size = self
1479 .texture_descriptor
1480 .format
1481 .pixel_size()
1482 .map_err(|_| TextureReinterpretationError::InvalidTextureFormat)?;
1483
1484 let tile_height_usize = tile_height as usize;
1486 let tile_width_usize = tile_width as usize;
1487
1488 for ty in 0..tiles_y {
1489 for tx in 0..tiles_x {
1490 for row in 0..tile_height_usize {
1491 let src_row = ty * tile_height_usize + row;
1492 let src_col = tx * tile_width_usize;
1493 let src_start = (src_row * image_width + src_col) * pixel_size;
1494 let src_end = src_start + tile_width_usize * pixel_size;
1495 new_data.extend_from_slice(&pixels[src_start..src_end]);
1496 }
1497 }
1498 }
1499
1500 Some(new_data)
1501 }
1502 None => None,
1503 };
1504
1505 let new_image = Image {
1508 data: new_data,
1509 data_order: self.data_order,
1510 texture_descriptor: TextureDescriptor {
1511 size: Extent3d {
1512 width: tile_width,
1513 height: tile_height,
1514 depth_or_array_layers: total_tiles as u32,
1515 },
1516 ..self.texture_descriptor.clone()
1517 },
1518 sampler: self.sampler.clone(),
1519 texture_view_descriptor: self.texture_view_descriptor.clone(),
1520 asset_usage: self.asset_usage,
1521 copy_on_resize: self.copy_on_resize,
1522 };
1523
1524 Ok(new_image)
1525 }
1526
1527 pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
1536 self.clone()
1537 .try_into_dynamic()
1538 .ok()
1539 .and_then(|img| match new_format {
1540 TextureFormat::R8Unorm => {
1541 Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
1542 }
1543 TextureFormat::Rg8Unorm => Some((
1544 image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
1545 false,
1546 )),
1547 TextureFormat::Rgba8UnormSrgb => {
1548 Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
1549 }
1550 _ => None,
1551 })
1552 .map(|(dyn_img, is_srgb)| Self::from_dynamic(dyn_img, is_srgb, self.asset_usage))
1553 }
1554
1555 pub fn from_buffer(
1558 buffer: &[u8],
1559 image_type: ImageType,
1560 #[cfg_attr(
1561 not(any(feature = "basis-universal", feature = "dds", feature = "ktx2")),
1562 expect(unused_variables, reason = "only used with certain features")
1563 )]
1564 supported_compressed_formats: CompressedImageFormats,
1565 is_srgb: bool,
1566 image_sampler: ImageSampler,
1567 asset_usage: RenderAssetUsages,
1568 ) -> Result<Image, TextureError> {
1569 let format = image_type.to_image_format()?;
1570
1571 let mut image = match format {
1578 #[cfg(feature = "basis-universal")]
1579 ImageFormat::Basis => {
1580 basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
1581 }
1582 #[cfg(feature = "dds")]
1583 ImageFormat::Dds => dds_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?,
1584 #[cfg(feature = "ktx2")]
1585 ImageFormat::Ktx2 => {
1586 ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
1587 }
1588 #[expect(
1589 clippy::allow_attributes,
1590 reason = "`unreachable_patterns` may not always lint"
1591 )]
1592 #[allow(
1593 unreachable_patterns,
1594 reason = "The wildcard pattern may be unreachable if only the specially-handled formats are enabled; however, the wildcard pattern is needed for any formats not specially handled"
1595 )]
1596 _ => {
1597 let image_crate_format = format
1598 .as_image_crate_format()
1599 .ok_or_else(|| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?;
1600 let mut reader = image::ImageReader::new(std::io::Cursor::new(buffer));
1601 reader.set_format(image_crate_format);
1602 reader.no_limits();
1603 let dyn_img = reader.decode()?;
1604 Self::from_dynamic(dyn_img, is_srgb, asset_usage)
1605 }
1606 };
1607 image.sampler = image_sampler;
1608 image.asset_usage = asset_usage;
1609 Ok(image)
1610 }
1611
1612 pub fn is_compressed(&self) -> bool {
1614 let format_description = self.texture_descriptor.format;
1615 format_description
1616 .required_features()
1617 .contains(Features::TEXTURE_COMPRESSION_ASTC)
1618 || format_description
1619 .required_features()
1620 .contains(Features::TEXTURE_COMPRESSION_ASTC_HDR)
1621 || format_description
1622 .required_features()
1623 .contains(Features::TEXTURE_COMPRESSION_BC)
1624 || format_description
1625 .required_features()
1626 .contains(Features::TEXTURE_COMPRESSION_ETC2)
1627 }
1628
1629 #[inline(always)]
1635 pub fn pixel_data_offset(&self, coords: UVec3) -> Result<usize, TextureAccessError> {
1636 let width = self.texture_descriptor.size.width;
1637 let height = self.texture_descriptor.size.height;
1638 let depth = self.texture_descriptor.size.depth_or_array_layers;
1639
1640 let pixel_size = self.texture_descriptor.format.pixel_size()?;
1641 let pixel_offset = match self.texture_descriptor.dimension {
1642 TextureDimension::D3 | TextureDimension::D2 => {
1643 if coords.x >= width || coords.y >= height || coords.z >= depth {
1644 return Err(TextureAccessError::OutOfBounds {
1645 x: coords.x,
1646 y: coords.y,
1647 z: coords.z,
1648 });
1649 }
1650 coords.z * height * width + coords.y * width + coords.x
1651 }
1652 TextureDimension::D1 => {
1653 if coords.x >= width {
1654 return Err(TextureAccessError::OutOfBounds {
1655 x: coords.x,
1656 y: coords.y,
1657 z: coords.z,
1658 });
1659 }
1660 coords.x
1661 }
1662 };
1663
1664 Ok(pixel_offset as usize * pixel_size)
1665 }
1666
1667 #[inline(always)]
1669 pub fn pixel_bytes(&self, coords: UVec3) -> Result<&[u8], TextureAccessError> {
1670 let len = self.texture_descriptor.format.pixel_size()?;
1671 let start = self.pixel_data_offset(coords)?;
1672 let Some(data) = self.data.as_ref() else {
1673 return Err(TextureAccessError::Uninitialized);
1674 };
1675 Ok(&data[start..(start + len)])
1676 }
1677
1678 #[inline(always)]
1680 pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Result<&mut [u8], TextureAccessError> {
1681 let len = self.texture_descriptor.format.pixel_size()?;
1682 let offset = self.pixel_data_offset(coords)?;
1683 let Some(data) = self.data.as_mut() else {
1684 return Err(TextureAccessError::Uninitialized);
1685 };
1686
1687 Ok(&mut data[offset..(offset + len)])
1688 }
1689
1690 pub fn clear(&mut self, pixel: &[u8]) {
1695 if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1696 && pixel_size > 0
1697 {
1698 let byte_len = pixel_size * pixel_count(self.texture_descriptor.size);
1699 debug_assert_eq!(
1700 pixel.len() % pixel_size,
1701 0,
1702 "Must not have incomplete pixel data (pixel size is {}B).",
1703 pixel_size,
1704 );
1705 debug_assert!(
1706 pixel.len() <= byte_len,
1707 "Clear data must fit within pixel buffer (expected {byte_len}B).",
1708 );
1709 if let Some(data) = self.data.as_mut() {
1710 for pixel_data in data.chunks_mut(pixel_size) {
1711 pixel_data.copy_from_slice(pixel);
1712 }
1713 }
1714 }
1715 }
1716
1717 #[inline(always)]
1721 pub fn get_color_at_1d(&self, x: u32) -> Result<Color, TextureAccessError> {
1722 if self.texture_descriptor.dimension != TextureDimension::D1 {
1723 return Err(TextureAccessError::WrongDimension);
1724 }
1725 self.get_color_at_internal(UVec3::new(x, 0, 0))
1726 }
1727
1728 #[inline(always)]
1752 pub fn get_color_at(&self, x: u32, y: u32) -> Result<Color, TextureAccessError> {
1753 if self.texture_descriptor.dimension != TextureDimension::D2 {
1754 return Err(TextureAccessError::WrongDimension);
1755 }
1756 self.get_color_at_internal(UVec3::new(x, y, 0))
1757 }
1758
1759 #[inline(always)]
1763 pub fn get_color_at_3d(&self, x: u32, y: u32, z: u32) -> Result<Color, TextureAccessError> {
1764 match (
1765 self.texture_descriptor.dimension,
1766 self.texture_descriptor.size.depth_or_array_layers,
1767 ) {
1768 (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1769 self.get_color_at_internal(UVec3::new(x, y, z))
1770 }
1771 _ => Err(TextureAccessError::WrongDimension),
1772 }
1773 }
1774
1775 #[inline(always)]
1779 pub fn set_color_at_1d(&mut self, x: u32, color: Color) -> Result<(), TextureAccessError> {
1780 if self.texture_descriptor.dimension != TextureDimension::D1 {
1781 return Err(TextureAccessError::WrongDimension);
1782 }
1783 self.set_color_at_internal(UVec3::new(x, 0, 0), color)
1784 }
1785
1786 #[inline(always)]
1811 pub fn set_color_at(&mut self, x: u32, y: u32, color: Color) -> Result<(), TextureAccessError> {
1812 if self.texture_descriptor.dimension != TextureDimension::D2 {
1813 return Err(TextureAccessError::WrongDimension);
1814 }
1815 self.set_color_at_internal(UVec3::new(x, y, 0), color)
1816 }
1817
1818 #[inline(always)]
1822 pub fn set_color_at_3d(
1823 &mut self,
1824 x: u32,
1825 y: u32,
1826 z: u32,
1827 color: Color,
1828 ) -> Result<(), TextureAccessError> {
1829 match (
1830 self.texture_descriptor.dimension,
1831 self.texture_descriptor.size.depth_or_array_layers,
1832 ) {
1833 (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1834 self.set_color_at_internal(UVec3::new(x, y, z), color)
1835 }
1836 _ => Err(TextureAccessError::WrongDimension),
1837 }
1838 }
1839
1840 #[inline(always)]
1841 fn get_color_at_internal(&self, coords: UVec3) -> Result<Color, TextureAccessError> {
1842 let bytes = self.pixel_bytes(coords)?;
1843
1844 match self.texture_descriptor.format {
1847 TextureFormat::Rgba8UnormSrgb => Ok(Color::srgba(
1848 bytes[0] as f32 / u8::MAX as f32,
1849 bytes[1] as f32 / u8::MAX as f32,
1850 bytes[2] as f32 / u8::MAX as f32,
1851 bytes[3] as f32 / u8::MAX as f32,
1852 )),
1853 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => Ok(Color::linear_rgba(
1854 bytes[0] as f32 / u8::MAX as f32,
1855 bytes[1] as f32 / u8::MAX as f32,
1856 bytes[2] as f32 / u8::MAX as f32,
1857 bytes[3] as f32 / u8::MAX as f32,
1858 )),
1859 TextureFormat::Bgra8UnormSrgb => Ok(Color::srgba(
1860 bytes[2] as f32 / u8::MAX as f32,
1861 bytes[1] as f32 / u8::MAX as f32,
1862 bytes[0] as f32 / u8::MAX as f32,
1863 bytes[3] as f32 / u8::MAX as f32,
1864 )),
1865 TextureFormat::Bgra8Unorm => Ok(Color::linear_rgba(
1866 bytes[2] as f32 / u8::MAX as f32,
1867 bytes[1] as f32 / u8::MAX as f32,
1868 bytes[0] as f32 / u8::MAX as f32,
1869 bytes[3] as f32 / u8::MAX as f32,
1870 )),
1871 TextureFormat::Rgba32Float => Ok(Color::linear_rgba(
1872 f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1873 f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1874 f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1875 f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1876 )),
1877 TextureFormat::Rgba16Float => Ok(Color::linear_rgba(
1878 half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32(),
1879 half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32(),
1880 half::f16::from_le_bytes([bytes[4], bytes[5]]).to_f32(),
1881 half::f16::from_le_bytes([bytes[6], bytes[7]]).to_f32(),
1882 )),
1883 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1884 let (r, g, b, a) = (
1885 u16::from_le_bytes([bytes[0], bytes[1]]),
1886 u16::from_le_bytes([bytes[2], bytes[3]]),
1887 u16::from_le_bytes([bytes[4], bytes[5]]),
1888 u16::from_le_bytes([bytes[6], bytes[7]]),
1889 );
1890 Ok(Color::linear_rgba(
1891 (r as f64 / u16::MAX as f64) as f32,
1893 (g as f64 / u16::MAX as f64) as f32,
1894 (b as f64 / u16::MAX as f64) as f32,
1895 (a as f64 / u16::MAX as f64) as f32,
1896 ))
1897 }
1898 TextureFormat::Rgba32Uint => {
1899 let (r, g, b, a) = (
1900 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1901 u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1902 u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1903 u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1904 );
1905 Ok(Color::linear_rgba(
1906 (r as f64 / u32::MAX as f64) as f32,
1908 (g as f64 / u32::MAX as f64) as f32,
1909 (b as f64 / u32::MAX as f64) as f32,
1910 (a as f64 / u32::MAX as f64) as f32,
1911 ))
1912 }
1913 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1916 let x = bytes[0] as f32 / u8::MAX as f32;
1917 Ok(Color::linear_rgb(x, x, x))
1918 }
1919 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1920 let x = u16::from_le_bytes([bytes[0], bytes[1]]);
1921 let x = (x as f64 / u16::MAX as f64) as f32;
1923 Ok(Color::linear_rgb(x, x, x))
1924 }
1925 TextureFormat::R32Uint => {
1926 let x = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1927 let x = (x as f64 / u32::MAX as f64) as f32;
1929 Ok(Color::linear_rgb(x, x, x))
1930 }
1931 TextureFormat::R16Float => {
1932 let x = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1933 Ok(Color::linear_rgb(x, x, x))
1934 }
1935 TextureFormat::R32Float => {
1936 let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1937 Ok(Color::linear_rgb(x, x, x))
1938 }
1939 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1940 let r = bytes[0] as f32 / u8::MAX as f32;
1941 let g = bytes[1] as f32 / u8::MAX as f32;
1942 Ok(Color::linear_rgb(r, g, 0.0))
1943 }
1944 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1945 let r = u16::from_le_bytes([bytes[0], bytes[1]]);
1946 let g = u16::from_le_bytes([bytes[2], bytes[3]]);
1947 let r = (r as f64 / u16::MAX as f64) as f32;
1949 let g = (g as f64 / u16::MAX as f64) as f32;
1950 Ok(Color::linear_rgb(r, g, 0.0))
1951 }
1952 TextureFormat::Rg32Uint => {
1953 let r = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1954 let g = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1955 let r = (r as f64 / u32::MAX as f64) as f32;
1957 let g = (g as f64 / u32::MAX as f64) as f32;
1958 Ok(Color::linear_rgb(r, g, 0.0))
1959 }
1960 TextureFormat::Rg16Float => {
1961 let r = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1962 let g = half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32();
1963 Ok(Color::linear_rgb(r, g, 0.0))
1964 }
1965 TextureFormat::Rg32Float => {
1966 let r = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1967 let g = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1968 Ok(Color::linear_rgb(r, g, 0.0))
1969 }
1970 _ => Err(TextureAccessError::UnsupportedTextureFormat(
1971 self.texture_descriptor.format,
1972 )),
1973 }
1974 }
1975
1976 #[inline(always)]
1977 fn set_color_at_internal(
1978 &mut self,
1979 coords: UVec3,
1980 color: Color,
1981 ) -> Result<(), TextureAccessError> {
1982 let format = self.texture_descriptor.format;
1983
1984 let bytes = self.pixel_bytes_mut(coords)?;
1985
1986 match format {
1989 TextureFormat::Rgba8UnormSrgb => {
1990 let [r, g, b, a] = Srgba::from(color).to_f32_array();
1991 bytes[0] = (r * u8::MAX as f32) as u8;
1992 bytes[1] = (g * u8::MAX as f32) as u8;
1993 bytes[2] = (b * u8::MAX as f32) as u8;
1994 bytes[3] = (a * u8::MAX as f32) as u8;
1995 }
1996 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => {
1997 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1998 bytes[0] = (r * u8::MAX as f32) as u8;
1999 bytes[1] = (g * u8::MAX as f32) as u8;
2000 bytes[2] = (b * u8::MAX as f32) as u8;
2001 bytes[3] = (a * u8::MAX as f32) as u8;
2002 }
2003 TextureFormat::Bgra8UnormSrgb => {
2004 let [r, g, b, a] = Srgba::from(color).to_f32_array();
2005 bytes[0] = (b * u8::MAX as f32) as u8;
2006 bytes[1] = (g * u8::MAX as f32) as u8;
2007 bytes[2] = (r * u8::MAX as f32) as u8;
2008 bytes[3] = (a * u8::MAX as f32) as u8;
2009 }
2010 TextureFormat::Bgra8Unorm => {
2011 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2012 bytes[0] = (b * u8::MAX as f32) as u8;
2013 bytes[1] = (g * u8::MAX as f32) as u8;
2014 bytes[2] = (r * u8::MAX as f32) as u8;
2015 bytes[3] = (a * u8::MAX as f32) as u8;
2016 }
2017 TextureFormat::Rgba16Float => {
2018 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2019 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
2020 bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
2021 bytes[4..6].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(b)));
2022 bytes[6..8].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(a)));
2023 }
2024 TextureFormat::Rgba32Float => {
2025 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2026 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
2027 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
2028 bytes[8..12].copy_from_slice(&f32::to_le_bytes(b));
2029 bytes[12..16].copy_from_slice(&f32::to_le_bytes(a));
2030 }
2031 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
2032 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2033 let [r, g, b, a] = [
2034 (r * u16::MAX as f32) as u16,
2035 (g * u16::MAX as f32) as u16,
2036 (b * u16::MAX as f32) as u16,
2037 (a * u16::MAX as f32) as u16,
2038 ];
2039 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
2040 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
2041 bytes[4..6].copy_from_slice(&u16::to_le_bytes(b));
2042 bytes[6..8].copy_from_slice(&u16::to_le_bytes(a));
2043 }
2044 TextureFormat::Rgba32Uint => {
2045 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2046 let [r, g, b, a] = [
2047 (r * u32::MAX as f32) as u32,
2048 (g * u32::MAX as f32) as u32,
2049 (b * u32::MAX as f32) as u32,
2050 (a * u32::MAX as f32) as u32,
2051 ];
2052 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
2053 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
2054 bytes[8..12].copy_from_slice(&u32::to_le_bytes(b));
2055 bytes[12..16].copy_from_slice(&u32::to_le_bytes(a));
2056 }
2057 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
2058 let linear = LinearRgba::from(color);
2060 let luminance = Xyza::from(linear).y;
2061 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
2062 bytes[0] = (r * u8::MAX as f32) as u8;
2063 }
2064 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
2065 let linear = LinearRgba::from(color);
2067 let luminance = Xyza::from(linear).y;
2068 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
2069 let r = (r * u16::MAX as f32) as u16;
2070 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
2071 }
2072 TextureFormat::R32Uint => {
2073 let linear = LinearRgba::from(color);
2075 let luminance = Xyza::from(linear).y;
2076 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
2077 let r = (r as f64 * u32::MAX as f64) as u32;
2079 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
2080 }
2081 TextureFormat::R16Float => {
2082 let linear = LinearRgba::from(color);
2084 let luminance = Xyza::from(linear).y;
2085 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
2086 let x = half::f16::from_f32(r);
2087 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(x));
2088 }
2089 TextureFormat::R32Float => {
2090 let linear = LinearRgba::from(color);
2092 let luminance = Xyza::from(linear).y;
2093 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
2094 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
2095 }
2096 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
2097 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
2098 bytes[0] = (r * u8::MAX as f32) as u8;
2099 bytes[1] = (g * u8::MAX as f32) as u8;
2100 }
2101 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
2102 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
2103 let r = (r * u16::MAX as f32) as u16;
2104 let g = (g * u16::MAX as f32) as u16;
2105 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
2106 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
2107 }
2108 TextureFormat::Rg32Uint => {
2109 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
2110 let r = (r as f64 * u32::MAX as f64) as u32;
2112 let g = (g as f64 * u32::MAX as f64) as u32;
2113 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
2114 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
2115 }
2116 TextureFormat::Rg16Float => {
2117 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
2118 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
2119 bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
2120 }
2121 TextureFormat::Rg32Float => {
2122 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
2123 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
2124 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
2125 }
2126 _ => {
2127 return Err(TextureAccessError::UnsupportedTextureFormat(
2128 self.texture_descriptor.format,
2129 ));
2130 }
2131 }
2132 Ok(())
2133 }
2134}
2135
2136#[derive(Clone, Copy, Debug)]
2140pub enum TextureChannelLayout {
2141 Rgb,
2143 Rgba,
2145 Rrr,
2147 Rrrg,
2149 Rg,
2151}
2152
2153#[derive(Clone, Copy, Debug)]
2155pub enum TranscodeFormat {
2156 Etc1s,
2158 Uastc(TextureChannelLayout),
2160 R8UnormSrgb,
2162 Rg8UnormSrgb,
2164 Rgb8,
2166}
2167
2168#[derive(Error, Debug)]
2170pub enum TextureReinterpretationError {
2171 #[error("incompatible sizes: old = {old:?} new = {new:?}")]
2173 IncompatibleSizes {
2174 old: Extent3d,
2176 new: Extent3d,
2178 },
2179 #[error("must be a 2d image")]
2181 WrongDimension,
2182 #[error("Rows * Columns must be > 1")]
2185 NotEnoughLayers,
2186 #[error("must not already be a layered image")]
2188 InvalidLayerCount,
2189 #[error("can not evenly divide height = {height} by layers = {layers}")]
2191 HeightNotDivisibleByLayers {
2192 height: u32,
2194 layers: u32,
2196 },
2197 #[error("can not evenly divide grid with height = {height} by tiles = {tile_count_y}")]
2199 GridHeightNotDivisibleByTileHeight {
2200 height: u32,
2202 tile_count_y: u32,
2204 },
2205 #[error("can not evenly divide grid with width = {width} by tiles = {tile_count_x}")]
2207 GridWidthNotDivisibleByTileWidth {
2208 width: u32,
2210 tile_count_x: u32,
2212 },
2213 #[error("Cannot process texture in its current format. Is it compressed?")]
2215 InvalidTextureFormat,
2216}
2217
2218#[derive(Error, Debug)]
2220pub enum TextureAccessError {
2221 #[error("out of bounds (x: {x}, y: {y}, z: {z})")]
2223 OutOfBounds {
2224 x: u32,
2226 y: u32,
2228 z: u32,
2230 },
2231 #[error("unsupported texture format: {0:?}")]
2235 UnsupportedTextureFormat(TextureFormat),
2236 #[error("image data is not initialized")]
2242 Uninitialized,
2243 #[error("attempt to access texture with different dimension")]
2245 WrongDimension,
2246}
2247
2248#[derive(Error, Debug)]
2250pub enum TextureError {
2251 #[error("invalid image mime type: {0}")]
2253 InvalidImageMimeType(String),
2254 #[error("invalid image extension: {0}")]
2256 InvalidImageExtension(String),
2257 #[error("failed to load an image: {0}")]
2259 ImageError(#[from] image::ImageError),
2260 #[error("unsupported texture format: {0}")]
2262 UnsupportedTextureFormat(String),
2263 #[error("supercompression not supported: {0}")]
2265 SuperCompressionNotSupported(String),
2266 #[error("failed to decompress an image: {0}")]
2268 SuperDecompressionError(String),
2269 #[error("invalid data: {0}")]
2271 InvalidData(String),
2272 #[error("transcode error: {0}")]
2274 TranscodeError(String),
2275 #[error("format requires transcoding: {0:?}")]
2277 FormatRequiresTranscodingError(TranscodeFormat),
2278 #[error("only cubemaps with six faces are supported")]
2280 IncompleteCubemap,
2281}
2282
2283#[derive(Debug)]
2285pub enum ImageType<'a> {
2286 MimeType(&'a str),
2288 Extension(&'a str),
2290 Format(ImageFormat),
2292}
2293
2294impl<'a> ImageType<'a> {
2295 pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
2302 match self {
2303 ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
2304 .ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
2305 ImageType::Extension(extension) => ImageFormat::from_extension(extension)
2306 .ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
2307 ImageType::Format(format) => Ok(*format),
2308 }
2309 }
2310}
2311
2312fn pixel_count(item: Extent3d) -> usize {
2314 (item.width * item.height * item.depth_or_array_layers) as usize
2315}
2316
2317pub trait TextureFormatPixelInfo {
2319 fn pixel_size(&self) -> Result<usize, TextureAccessError>;
2322}
2323
2324impl TextureFormatPixelInfo for TextureFormat {
2325 fn pixel_size(&self) -> Result<usize, TextureAccessError> {
2326 let info = self;
2327 match info.block_dimensions() {
2328 (1, 1) => Ok(info.block_copy_size(None).unwrap() as usize),
2329 _ => Err(TextureAccessError::UnsupportedTextureFormat(*self)),
2330 }
2331 }
2332}
2333
2334bitflags::bitflags! {
2335 #[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
2337 #[repr(transparent)]
2338 pub struct CompressedImageFormats: u32 {
2339 const NONE = 0;
2341 const ASTC_LDR = 1 << 0;
2349 const ASTC_HDR = 1 << 1;
2354 const BC = 1 << 2;
2366 const ETC2 = 1 << 3;
2374 }
2375}
2376
2377impl CompressedImageFormats {
2378 pub fn from_features(features: Features) -> Self {
2380 let mut supported_compressed_formats = Self::default();
2381 if features.contains(Features::TEXTURE_COMPRESSION_ASTC) {
2382 supported_compressed_formats |= Self::ASTC_LDR;
2383 }
2384 if features.contains(Features::TEXTURE_COMPRESSION_ASTC_HDR) {
2385 supported_compressed_formats |= Self::ASTC_HDR;
2386 }
2387 if features.contains(Features::TEXTURE_COMPRESSION_BC) {
2388 supported_compressed_formats |= Self::BC;
2389 }
2390 if features.contains(Features::TEXTURE_COMPRESSION_ETC2) {
2391 supported_compressed_formats |= Self::ETC2;
2392 }
2393 supported_compressed_formats
2394 }
2395
2396 pub fn supports(&self, format: TextureFormat) -> bool {
2401 match format {
2402 TextureFormat::Bc1RgbaUnorm
2403 | TextureFormat::Bc1RgbaUnormSrgb
2404 | TextureFormat::Bc2RgbaUnorm
2405 | TextureFormat::Bc2RgbaUnormSrgb
2406 | TextureFormat::Bc3RgbaUnorm
2407 | TextureFormat::Bc3RgbaUnormSrgb
2408 | TextureFormat::Bc4RUnorm
2409 | TextureFormat::Bc4RSnorm
2410 | TextureFormat::Bc5RgUnorm
2411 | TextureFormat::Bc5RgSnorm
2412 | TextureFormat::Bc6hRgbUfloat
2413 | TextureFormat::Bc6hRgbFloat
2414 | TextureFormat::Bc7RgbaUnorm
2415 | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
2416 TextureFormat::Etc2Rgb8Unorm
2417 | TextureFormat::Etc2Rgb8UnormSrgb
2418 | TextureFormat::Etc2Rgb8A1Unorm
2419 | TextureFormat::Etc2Rgb8A1UnormSrgb
2420 | TextureFormat::Etc2Rgba8Unorm
2421 | TextureFormat::Etc2Rgba8UnormSrgb
2422 | TextureFormat::EacR11Unorm
2423 | TextureFormat::EacR11Snorm
2424 | TextureFormat::EacRg11Unorm
2425 | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
2426 TextureFormat::Astc {
2427 channel: wgpu_types::AstcChannel::Hdr,
2428 ..
2429 } => self.contains(CompressedImageFormats::ASTC_HDR),
2430 TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
2431 _ => true,
2432 }
2433 }
2434}
2435
2436#[derive(Resource)]
2440pub struct CompressedImageFormatSupport(pub CompressedImageFormats);
2441
2442#[cfg(test)]
2443mod test {
2444 use super::*;
2445
2446 #[test]
2447 fn image_size() {
2448 let size = Extent3d {
2449 width: 200,
2450 height: 100,
2451 depth_or_array_layers: 1,
2452 };
2453 let image = Image::new_fill(
2454 size,
2455 TextureDimension::D2,
2456 &[0, 0, 0, 255],
2457 TextureFormat::Rgba8Unorm,
2458 RenderAssetUsages::MAIN_WORLD,
2459 );
2460 assert_eq!(
2461 Vec2::new(size.width as f32, size.height as f32),
2462 image.size_f32()
2463 );
2464 }
2465
2466 #[test]
2467 fn image_default_size() {
2468 let image = Image::default();
2469 assert_eq!(UVec2::ONE, image.size());
2470 assert_eq!(Vec2::ONE, image.size_f32());
2471 }
2472
2473 #[test]
2474 fn on_edge_pixel_is_invalid() {
2475 let image = Image::new_fill(
2476 Extent3d {
2477 width: 5,
2478 height: 10,
2479 depth_or_array_layers: 1,
2480 },
2481 TextureDimension::D2,
2482 &[0, 0, 0, 255],
2483 TextureFormat::Rgba8Unorm,
2484 RenderAssetUsages::MAIN_WORLD,
2485 );
2486 assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK)));
2487 assert!(matches!(
2488 image.get_color_at(0, 10),
2489 Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 })
2490 ));
2491 assert!(matches!(
2492 image.get_color_at(5, 10),
2493 Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 })
2494 ));
2495 }
2496
2497 #[test]
2498 fn compressed_texture_format_is_reported_correctly() {
2499 let mut image = Image::new_uninit(
2500 Extent3d {
2501 width: 4,
2502 height: 4,
2503 depth_or_array_layers: 1,
2504 },
2505 TextureDimension::D2,
2506 TextureFormat::Bc1RgbaUnorm,
2507 RenderAssetUsages::MAIN_WORLD,
2508 );
2509
2510 assert!(matches!(
2511 image.get_color_at(0, 0),
2512 Err(TextureAccessError::UnsupportedTextureFormat(
2513 TextureFormat::Bc1RgbaUnorm
2514 ))
2515 ));
2516
2517 assert!(matches!(
2518 image.set_color_at(0, 0, Color::WHITE),
2519 Err(TextureAccessError::UnsupportedTextureFormat(
2520 TextureFormat::Bc1RgbaUnorm
2521 ))
2522 ));
2523 }
2524
2525 #[test]
2526 fn get_set_pixel_2d_with_layers() {
2527 let mut image = Image::new_fill(
2528 Extent3d {
2529 width: 5,
2530 height: 10,
2531 depth_or_array_layers: 3,
2532 },
2533 TextureDimension::D2,
2534 &[0, 0, 0, 255],
2535 TextureFormat::Rgba8Unorm,
2536 RenderAssetUsages::MAIN_WORLD,
2537 );
2538 image.set_color_at_3d(0, 0, 0, Color::WHITE).unwrap();
2539 assert!(matches!(image.get_color_at_3d(0, 0, 0), Ok(Color::WHITE)));
2540 image.set_color_at_3d(2, 3, 1, Color::WHITE).unwrap();
2541 assert!(matches!(image.get_color_at_3d(2, 3, 1), Ok(Color::WHITE)));
2542 image.set_color_at_3d(4, 9, 2, Color::WHITE).unwrap();
2543 assert!(matches!(image.get_color_at_3d(4, 9, 2), Ok(Color::WHITE)));
2544 }
2545
2546 #[test]
2547 fn resize_in_place_2d_grow_and_shrink() {
2548 use bevy_color::ColorToPacked;
2549
2550 const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2551 const GROW_FILL: LinearRgba = LinearRgba::NONE;
2552
2553 let mut image = Image::new_fill(
2554 Extent3d {
2555 width: 2,
2556 height: 2,
2557 depth_or_array_layers: 1,
2558 },
2559 TextureDimension::D2,
2560 &INITIAL_FILL.to_u8_array(),
2561 TextureFormat::Rgba8Unorm,
2562 RenderAssetUsages::MAIN_WORLD,
2563 );
2564
2565 const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2568 (0, 1, LinearRgba::RED),
2569 (1, 1, LinearRgba::GREEN),
2570 (1, 0, LinearRgba::BLUE),
2571 ];
2572
2573 for (x, y, color) in &TEST_PIXELS {
2574 image.set_color_at(*x, *y, Color::from(*color)).unwrap();
2575 }
2576
2577 image.resize_in_place(Extent3d {
2579 width: 4,
2580 height: 4,
2581 depth_or_array_layers: 1,
2582 });
2583
2584 assert!(matches!(
2586 image.get_color_at(0, 0),
2587 Ok(Color::LinearRgba(INITIAL_FILL))
2588 ));
2589 for (x, y, color) in &TEST_PIXELS {
2590 assert_eq!(
2591 image.get_color_at(*x, *y).unwrap(),
2592 Color::LinearRgba(*color)
2593 );
2594 }
2595
2596 assert!(matches!(
2598 image.get_color_at(3, 3),
2599 Ok(Color::LinearRgba(GROW_FILL))
2600 ));
2601
2602 image.resize_in_place(Extent3d {
2604 width: 1,
2605 height: 1,
2606 depth_or_array_layers: 1,
2607 });
2608
2609 assert!(image.get_color_at(1, 1).is_err());
2611 }
2612
2613 #[test]
2614 fn resize_in_place_array_grow_and_shrink() {
2615 use bevy_color::ColorToPacked;
2616
2617 const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2618 const GROW_FILL: LinearRgba = LinearRgba::NONE;
2619 const LAYERS: u32 = 4;
2620
2621 let mut image = Image::new_fill(
2622 Extent3d {
2623 width: 2,
2624 height: 2,
2625 depth_or_array_layers: LAYERS,
2626 },
2627 TextureDimension::D2,
2628 &INITIAL_FILL.to_u8_array(),
2629 TextureFormat::Rgba8Unorm,
2630 RenderAssetUsages::MAIN_WORLD,
2631 );
2632
2633 const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2636 (0, 1, LinearRgba::RED),
2637 (1, 1, LinearRgba::GREEN),
2638 (1, 0, LinearRgba::BLUE),
2639 ];
2640
2641 for z in 0..LAYERS {
2642 for (x, y, color) in &TEST_PIXELS {
2643 image
2644 .set_color_at_3d(*x, *y, z, Color::from(*color))
2645 .unwrap();
2646 }
2647 }
2648
2649 image.resize_in_place(Extent3d {
2651 width: 4,
2652 height: 4,
2653 depth_or_array_layers: LAYERS + 1,
2654 });
2655
2656 assert!(matches!(
2658 image.get_color_at(0, 0),
2659 Ok(Color::LinearRgba(INITIAL_FILL))
2660 ));
2661 for z in 0..LAYERS {
2662 for (x, y, color) in &TEST_PIXELS {
2663 assert_eq!(
2664 image.get_color_at_3d(*x, *y, z).unwrap(),
2665 Color::LinearRgba(*color)
2666 );
2667 }
2668 }
2669
2670 for z in 0..(LAYERS + 1) {
2672 assert!(matches!(
2673 image.get_color_at_3d(3, 3, z),
2674 Ok(Color::LinearRgba(GROW_FILL))
2675 ));
2676 }
2677
2678 image.resize_in_place(Extent3d {
2680 width: 1,
2681 height: 1,
2682 depth_or_array_layers: 1,
2683 });
2684
2685 assert!(image.get_color_at_3d(1, 1, 0).is_err());
2687
2688 assert!(image.get_color_at_3d(0, 0, 1).is_err());
2690
2691 image.resize_in_place(Extent3d {
2693 width: 1,
2694 height: 1,
2695 depth_or_array_layers: 2,
2696 });
2697
2698 assert!(matches!(
2700 image.get_color_at_3d(0, 0, 1),
2701 Ok(Color::LinearRgba(GROW_FILL))
2702 ));
2703 }
2704
2705 #[test]
2706 fn create_array_from_2d_grid() {
2707 let image = Image::new_fill(
2709 Extent3d {
2710 width: 6,
2711 height: 6,
2712 depth_or_array_layers: 1,
2713 },
2714 TextureDimension::D2,
2715 &[0; 4],
2716 TextureFormat::Rgba8Snorm,
2717 RenderAssetUsages::all(),
2718 );
2719
2720 assert!(image.texture_descriptor.size.depth_or_array_layers == 1);
2721
2722 let new_array_image = image.create_stacked_array_from_2d_grid(2, 2).unwrap();
2723
2724 assert!(
2726 new_array_image
2727 .texture_descriptor
2728 .size
2729 .depth_or_array_layers
2730 == 2 * 2
2731 );
2732 assert!(
2733 new_array_image.data.unwrap().len()
2734 == pixel_count(new_array_image.texture_descriptor.size)
2735 * new_array_image
2736 .texture_descriptor
2737 .format
2738 .pixel_size()
2739 .unwrap()
2740 );
2741
2742 let image = Image::new_fill(
2744 Extent3d {
2745 width: 9,
2746 height: 2,
2747 depth_or_array_layers: 1,
2748 },
2749 TextureDimension::D2,
2750 &[0; 4],
2751 TextureFormat::Rgba8Snorm,
2752 RenderAssetUsages::all(),
2753 );
2754
2755 assert!(image.texture_descriptor.size.depth_or_array_layers == 1);
2756
2757 let new_array_image = image.create_stacked_array_from_2d_grid(1, 3).unwrap();
2758
2759 assert!(
2760 new_array_image
2761 .texture_descriptor
2762 .size
2763 .depth_or_array_layers
2764 == 3
2765 );
2766 assert!(
2767 new_array_image.data.unwrap().len()
2768 == pixel_count(new_array_image.texture_descriptor.size)
2769 * new_array_image
2770 .texture_descriptor
2771 .format
2772 .pixel_size()
2773 .unwrap()
2774 );
2775
2776 let image = Image::new_fill(
2777 Extent3d {
2778 width: 2,
2779 height: 1,
2780 depth_or_array_layers: 1,
2781 },
2782 TextureDimension::D2,
2783 &[0; 4],
2784 TextureFormat::Rgba8Snorm,
2785 RenderAssetUsages::all(),
2786 );
2787
2788 assert!(image.texture_descriptor.size.depth_or_array_layers == 1);
2789
2790 let new_array_image = image.create_stacked_array_from_2d_grid(1, 2).unwrap();
2791
2792 assert!(
2793 new_array_image
2794 .texture_descriptor
2795 .size
2796 .depth_or_array_layers
2797 == 2
2798 );
2799 assert!(
2800 new_array_image.data.unwrap().len()
2801 == pixel_count(new_array_image.texture_descriptor.size)
2802 * new_array_image
2803 .texture_descriptor
2804 .format
2805 .pixel_size()
2806 .unwrap()
2807 );
2808 }
2809
2810 #[test]
2811 fn image_clear() {
2812 let mut image = Image::new_fill(
2813 Extent3d {
2814 width: 32,
2815 height: 32,
2816 depth_or_array_layers: 1,
2817 },
2818 TextureDimension::D2,
2819 &[0; 4],
2820 TextureFormat::Rgba8Snorm,
2821 RenderAssetUsages::all(),
2822 );
2823
2824 assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 0));
2825
2826 image.clear(&[255; 4]);
2827
2828 assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 255));
2829 }
2830
2831 #[test]
2832 fn get_or_init_sampler_modifications() {
2833 let mut default_sampler = ImageSampler::Default;
2835 let my_sampler_in_a_loader = default_sampler
2837 .get_or_init_descriptor()
2838 .set_filter(ImageFilterMode::Linear)
2839 .set_address_mode(ImageAddressMode::Repeat);
2840
2841 assert_eq!(
2842 my_sampler_in_a_loader.address_mode_u,
2843 ImageAddressMode::Repeat
2844 );
2845 assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2846 }
2847
2848 #[test]
2849 fn get_or_init_sampler_anisotropy() {
2850 let mut default_sampler = ImageSampler::Default;
2852 let my_sampler_in_a_loader = default_sampler
2854 .get_or_init_descriptor()
2855 .set_anisotropic_filter(8);
2856
2857 assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2858 assert_eq!(my_sampler_in_a_loader.anisotropy_clamp, 8);
2859 }
2860}