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
30pub trait TextureSrgbViewFormats {
32 fn srgb_view_formats(&self) -> &'static [TextureFormat];
34}
35
36impl TextureSrgbViewFormats for TextureFormat {
37 fn srgb_view_formats(&self) -> &'static [TextureFormat] {
41 match self {
42 TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
43 TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb],
44 TextureFormat::Bc1RgbaUnorm => &[TextureFormat::Bc1RgbaUnormSrgb],
45 TextureFormat::Bc2RgbaUnorm => &[TextureFormat::Bc2RgbaUnormSrgb],
46 TextureFormat::Bc3RgbaUnorm => &[TextureFormat::Bc3RgbaUnormSrgb],
47 TextureFormat::Bc7RgbaUnorm => &[TextureFormat::Bc7RgbaUnormSrgb],
48 TextureFormat::Etc2Rgb8Unorm => &[TextureFormat::Etc2Rgb8UnormSrgb],
49 TextureFormat::Etc2Rgb8A1Unorm => &[TextureFormat::Etc2Rgb8A1UnormSrgb],
50 TextureFormat::Etc2Rgba8Unorm => &[TextureFormat::Etc2Rgba8UnormSrgb],
51 TextureFormat::Astc {
52 block: wgpu_types::AstcBlock::B4x4,
53 channel: wgpu_types::AstcChannel::Unorm,
54 } => &[TextureFormat::Astc {
55 block: wgpu_types::AstcBlock::B4x4,
56 channel: wgpu_types::AstcChannel::UnormSrgb,
57 }],
58 TextureFormat::Astc {
59 block: wgpu_types::AstcBlock::B5x4,
60 channel: wgpu_types::AstcChannel::Unorm,
61 } => &[TextureFormat::Astc {
62 block: wgpu_types::AstcBlock::B5x4,
63 channel: wgpu_types::AstcChannel::UnormSrgb,
64 }],
65 TextureFormat::Astc {
66 block: wgpu_types::AstcBlock::B5x5,
67 channel: wgpu_types::AstcChannel::Unorm,
68 } => &[TextureFormat::Astc {
69 block: wgpu_types::AstcBlock::B5x5,
70 channel: wgpu_types::AstcChannel::UnormSrgb,
71 }],
72 TextureFormat::Astc {
73 block: wgpu_types::AstcBlock::B6x5,
74 channel: wgpu_types::AstcChannel::Unorm,
75 } => &[TextureFormat::Astc {
76 block: wgpu_types::AstcBlock::B6x5,
77 channel: wgpu_types::AstcChannel::UnormSrgb,
78 }],
79 TextureFormat::Astc {
80 block: wgpu_types::AstcBlock::B6x6,
81 channel: wgpu_types::AstcChannel::Unorm,
82 } => &[TextureFormat::Astc {
83 block: wgpu_types::AstcBlock::B6x6,
84 channel: wgpu_types::AstcChannel::UnormSrgb,
85 }],
86 TextureFormat::Astc {
87 block: wgpu_types::AstcBlock::B8x5,
88 channel: wgpu_types::AstcChannel::Unorm,
89 } => &[TextureFormat::Astc {
90 block: wgpu_types::AstcBlock::B8x5,
91 channel: wgpu_types::AstcChannel::UnormSrgb,
92 }],
93 TextureFormat::Astc {
94 block: wgpu_types::AstcBlock::B8x6,
95 channel: wgpu_types::AstcChannel::Unorm,
96 } => &[TextureFormat::Astc {
97 block: wgpu_types::AstcBlock::B8x6,
98 channel: wgpu_types::AstcChannel::UnormSrgb,
99 }],
100 TextureFormat::Astc {
101 block: wgpu_types::AstcBlock::B8x8,
102 channel: wgpu_types::AstcChannel::Unorm,
103 } => &[TextureFormat::Astc {
104 block: wgpu_types::AstcBlock::B8x8,
105 channel: wgpu_types::AstcChannel::UnormSrgb,
106 }],
107 TextureFormat::Astc {
108 block: wgpu_types::AstcBlock::B10x5,
109 channel: wgpu_types::AstcChannel::Unorm,
110 } => &[TextureFormat::Astc {
111 block: wgpu_types::AstcBlock::B10x5,
112 channel: wgpu_types::AstcChannel::UnormSrgb,
113 }],
114 TextureFormat::Astc {
115 block: wgpu_types::AstcBlock::B10x6,
116 channel: wgpu_types::AstcChannel::Unorm,
117 } => &[TextureFormat::Astc {
118 block: wgpu_types::AstcBlock::B10x6,
119 channel: wgpu_types::AstcChannel::UnormSrgb,
120 }],
121 TextureFormat::Astc {
122 block: wgpu_types::AstcBlock::B10x8,
123 channel: wgpu_types::AstcChannel::Unorm,
124 } => &[TextureFormat::Astc {
125 block: wgpu_types::AstcBlock::B10x8,
126 channel: wgpu_types::AstcChannel::UnormSrgb,
127 }],
128 TextureFormat::Astc {
129 block: wgpu_types::AstcBlock::B10x10,
130 channel: wgpu_types::AstcChannel::Unorm,
131 } => &[TextureFormat::Astc {
132 block: wgpu_types::AstcBlock::B10x10,
133 channel: wgpu_types::AstcChannel::UnormSrgb,
134 }],
135 TextureFormat::Astc {
136 block: wgpu_types::AstcBlock::B12x10,
137 channel: wgpu_types::AstcChannel::Unorm,
138 } => &[TextureFormat::Astc {
139 block: wgpu_types::AstcBlock::B12x10,
140 channel: wgpu_types::AstcChannel::UnormSrgb,
141 }],
142 TextureFormat::Astc {
143 block: wgpu_types::AstcBlock::B12x12,
144 channel: wgpu_types::AstcChannel::Unorm,
145 } => &[TextureFormat::Astc {
146 block: wgpu_types::AstcBlock::B12x12,
147 channel: wgpu_types::AstcChannel::UnormSrgb,
148 }],
149 _ => &[],
150 }
151 }
152}
153
154pub const TRANSPARENT_IMAGE_HANDLE: Handle<Image> =
160 uuid_handle!("d18ad97e-a322-4981-9505-44c59a4b5e46");
161
162pub struct ImagePlugin {
164 pub default_sampler: ImageSamplerDescriptor,
166 #[cfg(any(
172 feature = "compressed_image_saver",
173 feature = "compressed_image_saver_universal"
174 ))]
175 pub default_compressed_image_processor_extensions: Vec<String>,
176}
177
178impl Default for ImagePlugin {
179 fn default() -> Self {
180 ImagePlugin {
181 default_sampler: ImageSamplerDescriptor::linear(),
182 #[cfg(any(
183 feature = "compressed_image_saver",
184 feature = "compressed_image_saver_universal"
185 ))]
186 default_compressed_image_processor_extensions: [
187 "png".into(),
188 "jpeg".into(),
189 "jpg".into(),
190 ]
191 .into(),
192 }
193 }
194}
195
196impl ImagePlugin {
197 pub fn with_default_sampler(mut self, value: ImageSamplerDescriptor) -> ImagePlugin {
199 self.default_sampler = value;
200 self
201 }
202
203 pub fn default_linear() -> ImagePlugin {
205 Default::default()
206 }
207
208 pub fn default_nearest() -> ImagePlugin {
210 ImagePlugin::default().with_default_sampler(ImageSamplerDescriptor::nearest())
211 }
212}
213
214impl Plugin for ImagePlugin {
215 fn build(&self, app: &mut App) {
216 #[cfg(feature = "exr")]
217 app.init_asset_loader::<crate::ExrTextureLoader>();
218
219 #[cfg(feature = "hdr")]
220 app.init_asset_loader::<crate::HdrTextureLoader>();
221
222 app.init_asset::<Image>();
223 #[cfg(feature = "bevy_reflect")]
224 app.register_asset_reflect::<Image>();
225
226 let mut image_assets = app.world_mut().resource_mut::<Assets<Image>>();
227
228 image_assets
229 .insert(&Handle::default(), Image::default())
230 .unwrap();
231 image_assets
232 .insert(&TRANSPARENT_IMAGE_HANDLE, Image::transparent())
233 .unwrap();
234
235 #[cfg(any(
236 feature = "compressed_image_saver",
237 feature = "compressed_image_saver_universal"
238 ))]
239 if let Some(processor) = app
240 .world()
241 .get_resource::<bevy_asset::processor::AssetProcessor>()
242 {
243 processor.register_processor::<bevy_asset::processor::LoadTransformAndSave<
244 ImageLoader,
245 bevy_asset::transformer::IdentityAssetTransformer<Image>,
246 crate::CompressedImageSaver,
247 >>(crate::CompressedImageSaver::default().into());
248
249 for file_extension in &self.default_compressed_image_processor_extensions {
250 processor.set_default_processor::<bevy_asset::processor::LoadTransformAndSave<
251 ImageLoader,
252 bevy_asset::transformer::IdentityAssetTransformer<Image>,
253 crate::CompressedImageSaver,
254 >>(file_extension);
255 }
256 }
257
258 app.preregister_asset_loader::<ImageLoader>(ImageLoader::SUPPORTED_FILE_EXTENSIONS);
259 }
260}
261
262#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
264pub enum ImageFormat {
265 #[cfg(feature = "basis-universal")]
267 Basis,
268 #[cfg(feature = "bmp")]
270 Bmp,
271 #[cfg(feature = "dds")]
273 Dds,
274 #[cfg(feature = "ff")]
276 Farbfeld,
277 #[cfg(feature = "gif")]
279 Gif,
280 #[cfg(feature = "exr")]
282 OpenExr,
283 #[cfg(feature = "hdr")]
285 Hdr,
286 #[cfg(feature = "ico")]
288 Ico,
289 #[cfg(feature = "jpeg")]
291 Jpeg,
292 #[cfg(feature = "ktx2")]
294 Ktx2,
295 #[cfg(feature = "png")]
297 Png,
298 #[cfg(feature = "pnm")]
300 Pnm,
301 #[cfg(feature = "qoi")]
303 Qoi,
304 #[cfg(feature = "tga")]
306 Tga,
307 #[cfg(feature = "tiff")]
309 Tiff,
310 #[cfg(feature = "webp")]
312 WebP,
313}
314
315macro_rules! feature_gate {
316 ($feature: tt, $value: ident) => {{
317 #[cfg(not(feature = $feature))]
318 {
319 tracing::warn!("feature \"{}\" is not enabled", $feature);
320 return None;
321 }
322 #[cfg(feature = $feature)]
323 ImageFormat::$value
324 }};
325}
326
327impl ImageFormat {
328 pub const fn to_file_extensions(&self) -> &'static [&'static str] {
330 match self {
331 #[cfg(feature = "basis-universal")]
332 ImageFormat::Basis => &["basis"],
333 #[cfg(feature = "bmp")]
334 ImageFormat::Bmp => &["bmp"],
335 #[cfg(feature = "dds")]
336 ImageFormat::Dds => &["dds"],
337 #[cfg(feature = "ff")]
338 ImageFormat::Farbfeld => &["ff", "farbfeld"],
339 #[cfg(feature = "gif")]
340 ImageFormat::Gif => &["gif"],
341 #[cfg(feature = "exr")]
342 ImageFormat::OpenExr => &["exr"],
343 #[cfg(feature = "hdr")]
344 ImageFormat::Hdr => &["hdr"],
345 #[cfg(feature = "ico")]
346 ImageFormat::Ico => &["ico"],
347 #[cfg(feature = "jpeg")]
348 ImageFormat::Jpeg => &["jpg", "jpeg"],
349 #[cfg(feature = "ktx2")]
350 ImageFormat::Ktx2 => &["ktx2"],
351 #[cfg(feature = "pnm")]
352 ImageFormat::Pnm => &["pam", "pbm", "pgm", "ppm"],
353 #[cfg(feature = "png")]
354 ImageFormat::Png => &["png"],
355 #[cfg(feature = "qoi")]
356 ImageFormat::Qoi => &["qoi"],
357 #[cfg(feature = "tga")]
358 ImageFormat::Tga => &["tga"],
359 #[cfg(feature = "tiff")]
360 ImageFormat::Tiff => &["tif", "tiff"],
361 #[cfg(feature = "webp")]
362 ImageFormat::WebP => &["webp"],
363 #[expect(
365 clippy::allow_attributes,
366 reason = "`unreachable_patterns` may not always lint"
367 )]
368 #[allow(
369 unreachable_patterns,
370 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
371 )]
372 _ => &[],
373 }
374 }
375
376 pub const fn to_mime_types(&self) -> &'static [&'static str] {
380 match self {
381 #[cfg(feature = "basis-universal")]
382 ImageFormat::Basis => &["image/basis", "image/x-basis"],
383 #[cfg(feature = "bmp")]
384 ImageFormat::Bmp => &["image/bmp", "image/x-bmp"],
385 #[cfg(feature = "dds")]
386 ImageFormat::Dds => &["image/vnd-ms.dds"],
387 #[cfg(feature = "hdr")]
388 ImageFormat::Hdr => &["image/vnd.radiance"],
389 #[cfg(feature = "gif")]
390 ImageFormat::Gif => &["image/gif"],
391 #[cfg(feature = "ff")]
392 ImageFormat::Farbfeld => &[],
393 #[cfg(feature = "ico")]
394 ImageFormat::Ico => &["image/x-icon"],
395 #[cfg(feature = "jpeg")]
396 ImageFormat::Jpeg => &["image/jpeg"],
397 #[cfg(feature = "ktx2")]
398 ImageFormat::Ktx2 => &["image/ktx2"],
399 #[cfg(feature = "png")]
400 ImageFormat::Png => &["image/png"],
401 #[cfg(feature = "qoi")]
402 ImageFormat::Qoi => &["image/qoi", "image/x-qoi"],
403 #[cfg(feature = "exr")]
404 ImageFormat::OpenExr => &["image/x-exr"],
405 #[cfg(feature = "pnm")]
406 ImageFormat::Pnm => &[
407 "image/x-portable-bitmap",
408 "image/x-portable-graymap",
409 "image/x-portable-pixmap",
410 "image/x-portable-anymap",
411 ],
412 #[cfg(feature = "tga")]
413 ImageFormat::Tga => &["image/x-targa", "image/x-tga"],
414 #[cfg(feature = "tiff")]
415 ImageFormat::Tiff => &["image/tiff"],
416 #[cfg(feature = "webp")]
417 ImageFormat::WebP => &["image/webp"],
418 #[expect(
420 clippy::allow_attributes,
421 reason = "`unreachable_patterns` may not always lint"
422 )]
423 #[allow(
424 unreachable_patterns,
425 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
426 )]
427 _ => &[],
428 }
429 }
430
431 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
435 #[expect(
436 clippy::allow_attributes,
437 reason = "`unreachable_code` may not always lint"
438 )]
439 #[allow(
440 unreachable_code,
441 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
442 )]
443 Some(match mime_type.to_ascii_lowercase().as_str() {
444 "image/basis" | "image/x-basis" => feature_gate!("basis-universal", Basis),
446 "image/bmp" | "image/x-bmp" => feature_gate!("bmp", Bmp),
447 "image/vnd-ms.dds" => feature_gate!("dds", Dds),
448 "image/vnd.radiance" => feature_gate!("hdr", Hdr),
449 "image/gif" => feature_gate!("gif", Gif),
450 "image/x-icon" => feature_gate!("ico", Ico),
451 "image/jpeg" => feature_gate!("jpeg", Jpeg),
452 "image/ktx2" => feature_gate!("ktx2", Ktx2),
453 "image/png" => feature_gate!("png", Png),
454 "image/qoi" | "image/x-qoi" => feature_gate!("qoi", Qoi),
455 "image/x-exr" => feature_gate!("exr", OpenExr),
456 "image/x-portable-bitmap"
457 | "image/x-portable-graymap"
458 | "image/x-portable-pixmap"
459 | "image/x-portable-anymap" => feature_gate!("pnm", Pnm),
460 "image/x-targa" | "image/x-tga" => feature_gate!("tga", Tga),
461 "image/tiff" => feature_gate!("tiff", Tiff),
462 "image/webp" => feature_gate!("webp", WebP),
463 _ => return None,
464 })
465 }
466
467 pub fn from_extension(extension: &str) -> Option<Self> {
471 #[expect(
472 clippy::allow_attributes,
473 reason = "`unreachable_code` may not always lint"
474 )]
475 #[allow(
476 unreachable_code,
477 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
478 )]
479 Some(match extension.to_ascii_lowercase().as_str() {
480 "basis" => feature_gate!("basis-universal", Basis),
481 "bmp" => feature_gate!("bmp", Bmp),
482 "dds" => feature_gate!("dds", Dds),
483 "ff" | "farbfeld" => feature_gate!("ff", Farbfeld),
484 "gif" => feature_gate!("gif", Gif),
485 "exr" => feature_gate!("exr", OpenExr),
486 "hdr" => feature_gate!("hdr", Hdr),
487 "ico" => feature_gate!("ico", Ico),
488 "jpg" | "jpeg" => feature_gate!("jpeg", Jpeg),
489 "ktx2" => feature_gate!("ktx2", Ktx2),
490 "pam" | "pbm" | "pgm" | "ppm" => feature_gate!("pnm", Pnm),
491 "png" => feature_gate!("png", Png),
492 "qoi" => feature_gate!("qoi", Qoi),
493 "tga" => feature_gate!("tga", Tga),
494 "tif" | "tiff" => feature_gate!("tiff", Tiff),
495 "webp" => feature_gate!("webp", WebP),
496 _ => return None,
497 })
498 }
499
500 pub fn as_image_crate_format(&self) -> Option<image::ImageFormat> {
502 #[expect(
503 clippy::allow_attributes,
504 reason = "`unreachable_code` may not always lint"
505 )]
506 #[allow(
507 unreachable_code,
508 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
509 )]
510 Some(match self {
511 #[cfg(feature = "bmp")]
512 ImageFormat::Bmp => image::ImageFormat::Bmp,
513 #[cfg(feature = "dds")]
514 ImageFormat::Dds => image::ImageFormat::Dds,
515 #[cfg(feature = "ff")]
516 ImageFormat::Farbfeld => image::ImageFormat::Farbfeld,
517 #[cfg(feature = "gif")]
518 ImageFormat::Gif => image::ImageFormat::Gif,
519 #[cfg(feature = "exr")]
520 ImageFormat::OpenExr => image::ImageFormat::OpenExr,
521 #[cfg(feature = "hdr")]
522 ImageFormat::Hdr => image::ImageFormat::Hdr,
523 #[cfg(feature = "ico")]
524 ImageFormat::Ico => image::ImageFormat::Ico,
525 #[cfg(feature = "jpeg")]
526 ImageFormat::Jpeg => image::ImageFormat::Jpeg,
527 #[cfg(feature = "png")]
528 ImageFormat::Png => image::ImageFormat::Png,
529 #[cfg(feature = "pnm")]
530 ImageFormat::Pnm => image::ImageFormat::Pnm,
531 #[cfg(feature = "qoi")]
532 ImageFormat::Qoi => image::ImageFormat::Qoi,
533 #[cfg(feature = "tga")]
534 ImageFormat::Tga => image::ImageFormat::Tga,
535 #[cfg(feature = "tiff")]
536 ImageFormat::Tiff => image::ImageFormat::Tiff,
537 #[cfg(feature = "webp")]
538 ImageFormat::WebP => image::ImageFormat::WebP,
539 #[cfg(feature = "basis-universal")]
540 ImageFormat::Basis => return None,
541 #[cfg(feature = "ktx2")]
542 ImageFormat::Ktx2 => return None,
543 #[expect(
545 clippy::allow_attributes,
546 reason = "`unreachable_patterns` may not always lint"
547 )]
548 #[allow(
549 unreachable_patterns,
550 reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
551 )]
552 _ => return None,
553 })
554 }
555
556 pub fn from_image_crate_format(format: image::ImageFormat) -> Option<ImageFormat> {
560 #[expect(
561 clippy::allow_attributes,
562 reason = "`unreachable_code` may not always lint"
563 )]
564 #[allow(
565 unreachable_code,
566 reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
567 )]
568 Some(match format {
569 image::ImageFormat::Bmp => feature_gate!("bmp", Bmp),
570 image::ImageFormat::Dds => feature_gate!("dds", Dds),
571 image::ImageFormat::Farbfeld => feature_gate!("ff", Farbfeld),
572 image::ImageFormat::Gif => feature_gate!("gif", Gif),
573 image::ImageFormat::OpenExr => feature_gate!("exr", OpenExr),
574 image::ImageFormat::Hdr => feature_gate!("hdr", Hdr),
575 image::ImageFormat::Ico => feature_gate!("ico", Ico),
576 image::ImageFormat::Jpeg => feature_gate!("jpeg", Jpeg),
577 image::ImageFormat::Png => feature_gate!("png", Png),
578 image::ImageFormat::Pnm => feature_gate!("pnm", Pnm),
579 image::ImageFormat::Qoi => feature_gate!("qoi", Qoi),
580 image::ImageFormat::Tga => feature_gate!("tga", Tga),
581 image::ImageFormat::Tiff => feature_gate!("tiff", Tiff),
582 image::ImageFormat::WebP => feature_gate!("webp", WebP),
583 _ => return None,
584 })
585 }
586}
587
588pub trait ToExtents {
590 fn to_extents(self) -> Extent3d;
592}
593impl ToExtents for UVec2 {
594 fn to_extents(self) -> Extent3d {
595 Extent3d {
596 width: self.x,
597 height: self.y,
598 depth_or_array_layers: 1,
599 }
600 }
601}
602impl ToExtents for UVec3 {
603 fn to_extents(self) -> Extent3d {
604 Extent3d {
605 width: self.x,
606 height: self.y,
607 depth_or_array_layers: self.z,
608 }
609 }
610}
611
612#[cfg_attr(
618 feature = "serialize",
619 doc = "[`SerializedImage`](crate::SerializedImage)."
620)]
621#[cfg_attr(
622 not(feature = "serialize"),
623 doc = "`SerializedImage`, which requires the `serialize` feature."
624)]
625#[derive(Asset, Debug, Clone, PartialEq)]
627#[cfg_attr(
628 feature = "bevy_reflect",
629 derive(Reflect),
630 reflect(opaque, Default, Debug, Clone)
631)]
632#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
633#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))]
634pub struct Image {
635 pub data: Option<Vec<u8>>,
640 pub data_order: TextureDataOrder,
644 pub texture_descriptor: TextureDescriptor<Option<&'static str>, &'static [TextureFormat]>,
653 pub sampler: ImageSampler,
655 pub texture_view_descriptor: Option<TextureViewDescriptor<Option<&'static str>>>,
663 pub asset_usage: RenderAssetUsages,
665 pub copy_on_resize: bool,
667}
668
669#[cfg(feature = "serialize")]
670mod image_serde {
671 use super::*;
672
673 impl Serialize for Image {
674 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
675 super::super::serialized_image::SerializedImage::from_image(self.clone())
676 .serialize(serializer)
677 }
678 }
679
680 impl<'de> Deserialize<'de> for Image {
681 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
682 super::super::serialized_image::SerializedImage::deserialize(deserializer)
683 .map(super::super::serialized_image::SerializedImage::into_image)
684 }
685 }
686}
687
688#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
692#[cfg_attr(
693 feature = "bevy_reflect",
694 derive(Reflect),
695 reflect(Default, Debug, Clone)
696)]
697pub enum ImageSampler {
698 #[default]
700 Default,
701 Descriptor(ImageSamplerDescriptor),
703}
704
705impl ImageSampler {
706 #[inline]
708 pub fn linear() -> ImageSampler {
709 ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
710 }
711
712 #[inline]
714 pub fn nearest() -> ImageSampler {
715 ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
716 }
717
718 pub fn get_or_init_descriptor(&mut self) -> &mut ImageSamplerDescriptor {
723 match self {
724 ImageSampler::Default => {
725 *self = ImageSampler::Descriptor(ImageSamplerDescriptor::default());
726 match self {
727 ImageSampler::Descriptor(descriptor) => descriptor,
728 _ => unreachable!(),
729 }
730 }
731 ImageSampler::Descriptor(descriptor) => descriptor,
732 }
733 }
734}
735
736#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
742#[cfg_attr(
743 feature = "bevy_reflect",
744 derive(Reflect),
745 reflect(Default, Debug, Clone)
746)]
747pub enum ImageAddressMode {
748 #[default]
753 ClampToEdge,
754 Repeat,
759 MirrorRepeat,
764 ClampToBorder,
770}
771
772#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
776#[cfg_attr(
777 feature = "bevy_reflect",
778 derive(Reflect),
779 reflect(Default, Debug, Clone)
780)]
781pub enum ImageFilterMode {
782 #[default]
786 Nearest,
787 Linear,
791}
792
793#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
797#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
798pub enum ImageCompareFunction {
799 Never,
801 Less,
803 Equal,
807 LessEqual,
809 Greater,
811 NotEqual,
815 GreaterEqual,
817 Always,
819}
820
821#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
825#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
826pub enum ImageSamplerBorderColor {
827 TransparentBlack,
829 OpaqueBlack,
831 OpaqueWhite,
833 Zero,
839}
840
841#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
849#[cfg_attr(
850 feature = "bevy_reflect",
851 derive(Reflect),
852 reflect(Default, Debug, Clone)
853)]
854pub struct ImageSamplerDescriptor {
855 pub label: Option<String>,
857 pub address_mode_u: ImageAddressMode,
859 pub address_mode_v: ImageAddressMode,
861 pub address_mode_w: ImageAddressMode,
863 pub mag_filter: ImageFilterMode,
865 pub min_filter: ImageFilterMode,
867 pub mipmap_filter: ImageFilterMode,
869 pub lod_min_clamp: f32,
871 pub lod_max_clamp: f32,
873 pub compare: Option<ImageCompareFunction>,
875 pub anisotropy_clamp: u16,
877 pub border_color: Option<ImageSamplerBorderColor>,
879}
880
881impl Default for ImageSamplerDescriptor {
882 fn default() -> Self {
883 Self {
884 address_mode_u: Default::default(),
885 address_mode_v: Default::default(),
886 address_mode_w: Default::default(),
887 mag_filter: Default::default(),
888 min_filter: Default::default(),
889 mipmap_filter: Default::default(),
890 lod_min_clamp: 0.0,
891 lod_max_clamp: 32.0,
892 compare: None,
893 anisotropy_clamp: 1,
894 border_color: None,
895 label: None,
896 }
897 }
898}
899
900impl ImageSamplerDescriptor {
901 #[inline]
903 pub fn linear() -> ImageSamplerDescriptor {
904 ImageSamplerDescriptor {
905 mag_filter: ImageFilterMode::Linear,
906 min_filter: ImageFilterMode::Linear,
907 mipmap_filter: ImageFilterMode::Linear,
908 ..Default::default()
909 }
910 }
911
912 #[inline]
914 pub fn nearest() -> ImageSamplerDescriptor {
915 ImageSamplerDescriptor {
916 mag_filter: ImageFilterMode::Nearest,
917 min_filter: ImageFilterMode::Nearest,
918 mipmap_filter: ImageFilterMode::Nearest,
919 ..Default::default()
920 }
921 }
922
923 #[inline]
925 pub fn set_filter(&mut self, filter: ImageFilterMode) -> &mut Self {
926 self.mag_filter = filter;
927 self.min_filter = filter;
928 self.mipmap_filter = filter;
929 self
930 }
931
932 #[inline]
934 pub fn set_address_mode(&mut self, address_mode: ImageAddressMode) -> &mut Self {
935 self.address_mode_u = address_mode;
936 self.address_mode_v = address_mode;
937 self.address_mode_w = address_mode;
938 self
939 }
940
941 #[inline]
945 pub fn set_anisotropic_filter(&mut self, anisotropy_clamp: u16) -> &mut Self {
946 self.mag_filter = ImageFilterMode::Linear;
947 self.min_filter = ImageFilterMode::Linear;
948 self.mipmap_filter = ImageFilterMode::Linear;
949 self.anisotropy_clamp = anisotropy_clamp;
950 self
951 }
952
953 pub fn as_wgpu(&self) -> SamplerDescriptor<Option<&str>> {
955 SamplerDescriptor {
956 label: self.label.as_deref(),
957 address_mode_u: self.address_mode_u.into(),
958 address_mode_v: self.address_mode_v.into(),
959 address_mode_w: self.address_mode_w.into(),
960 mag_filter: self.mag_filter.into(),
961 min_filter: self.min_filter.into(),
962 mipmap_filter: self.mipmap_filter.into(),
963 lod_min_clamp: self.lod_min_clamp,
964 lod_max_clamp: self.lod_max_clamp,
965 compare: self.compare.map(Into::into),
966 anisotropy_clamp: self.anisotropy_clamp,
967 border_color: self.border_color.map(Into::into),
968 }
969 }
970}
971
972impl From<ImageAddressMode> for AddressMode {
973 fn from(value: ImageAddressMode) -> Self {
974 match value {
975 ImageAddressMode::ClampToEdge => AddressMode::ClampToEdge,
976 ImageAddressMode::Repeat => AddressMode::Repeat,
977 ImageAddressMode::MirrorRepeat => AddressMode::MirrorRepeat,
978 ImageAddressMode::ClampToBorder => AddressMode::ClampToBorder,
979 }
980 }
981}
982
983impl From<ImageFilterMode> for FilterMode {
984 fn from(value: ImageFilterMode) -> Self {
985 match value {
986 ImageFilterMode::Nearest => FilterMode::Nearest,
987 ImageFilterMode::Linear => FilterMode::Linear,
988 }
989 }
990}
991
992impl From<ImageFilterMode> for MipmapFilterMode {
993 fn from(value: ImageFilterMode) -> Self {
994 match value {
995 ImageFilterMode::Nearest => MipmapFilterMode::Nearest,
996 ImageFilterMode::Linear => MipmapFilterMode::Linear,
997 }
998 }
999}
1000
1001impl From<ImageCompareFunction> for CompareFunction {
1002 fn from(value: ImageCompareFunction) -> Self {
1003 match value {
1004 ImageCompareFunction::Never => CompareFunction::Never,
1005 ImageCompareFunction::Less => CompareFunction::Less,
1006 ImageCompareFunction::Equal => CompareFunction::Equal,
1007 ImageCompareFunction::LessEqual => CompareFunction::LessEqual,
1008 ImageCompareFunction::Greater => CompareFunction::Greater,
1009 ImageCompareFunction::NotEqual => CompareFunction::NotEqual,
1010 ImageCompareFunction::GreaterEqual => CompareFunction::GreaterEqual,
1011 ImageCompareFunction::Always => CompareFunction::Always,
1012 }
1013 }
1014}
1015
1016impl From<ImageSamplerBorderColor> for SamplerBorderColor {
1017 fn from(value: ImageSamplerBorderColor) -> Self {
1018 match value {
1019 ImageSamplerBorderColor::TransparentBlack => SamplerBorderColor::TransparentBlack,
1020 ImageSamplerBorderColor::OpaqueBlack => SamplerBorderColor::OpaqueBlack,
1021 ImageSamplerBorderColor::OpaqueWhite => SamplerBorderColor::OpaqueWhite,
1022 ImageSamplerBorderColor::Zero => SamplerBorderColor::Zero,
1023 }
1024 }
1025}
1026
1027impl From<AddressMode> for ImageAddressMode {
1028 fn from(value: AddressMode) -> Self {
1029 match value {
1030 AddressMode::ClampToEdge => ImageAddressMode::ClampToEdge,
1031 AddressMode::Repeat => ImageAddressMode::Repeat,
1032 AddressMode::MirrorRepeat => ImageAddressMode::MirrorRepeat,
1033 AddressMode::ClampToBorder => ImageAddressMode::ClampToBorder,
1034 }
1035 }
1036}
1037
1038impl From<MipmapFilterMode> for ImageFilterMode {
1039 fn from(value: MipmapFilterMode) -> Self {
1040 match value {
1041 MipmapFilterMode::Nearest => ImageFilterMode::Nearest,
1042 MipmapFilterMode::Linear => ImageFilterMode::Linear,
1043 }
1044 }
1045}
1046
1047impl From<FilterMode> for ImageFilterMode {
1048 fn from(value: FilterMode) -> Self {
1049 match value {
1050 FilterMode::Nearest => ImageFilterMode::Nearest,
1051 FilterMode::Linear => ImageFilterMode::Linear,
1052 }
1053 }
1054}
1055
1056impl From<CompareFunction> for ImageCompareFunction {
1057 fn from(value: CompareFunction) -> Self {
1058 match value {
1059 CompareFunction::Never => ImageCompareFunction::Never,
1060 CompareFunction::Less => ImageCompareFunction::Less,
1061 CompareFunction::Equal => ImageCompareFunction::Equal,
1062 CompareFunction::LessEqual => ImageCompareFunction::LessEqual,
1063 CompareFunction::Greater => ImageCompareFunction::Greater,
1064 CompareFunction::NotEqual => ImageCompareFunction::NotEqual,
1065 CompareFunction::GreaterEqual => ImageCompareFunction::GreaterEqual,
1066 CompareFunction::Always => ImageCompareFunction::Always,
1067 }
1068 }
1069}
1070
1071impl From<SamplerBorderColor> for ImageSamplerBorderColor {
1072 fn from(value: SamplerBorderColor) -> Self {
1073 match value {
1074 SamplerBorderColor::TransparentBlack => ImageSamplerBorderColor::TransparentBlack,
1075 SamplerBorderColor::OpaqueBlack => ImageSamplerBorderColor::OpaqueBlack,
1076 SamplerBorderColor::OpaqueWhite => ImageSamplerBorderColor::OpaqueWhite,
1077 SamplerBorderColor::Zero => ImageSamplerBorderColor::Zero,
1078 }
1079 }
1080}
1081
1082impl From<SamplerDescriptor<Option<&str>>> for ImageSamplerDescriptor {
1083 fn from(value: SamplerDescriptor<Option<&str>>) -> Self {
1084 ImageSamplerDescriptor {
1085 label: value.label.map(ToString::to_string),
1086 address_mode_u: value.address_mode_u.into(),
1087 address_mode_v: value.address_mode_v.into(),
1088 address_mode_w: value.address_mode_w.into(),
1089 mag_filter: value.mag_filter.into(),
1090 min_filter: value.min_filter.into(),
1091 mipmap_filter: value.mipmap_filter.into(),
1092 lod_min_clamp: value.lod_min_clamp,
1093 lod_max_clamp: value.lod_max_clamp,
1094 compare: value.compare.map(Into::into),
1095 anisotropy_clamp: value.anisotropy_clamp,
1096 border_color: value.border_color.map(Into::into),
1097 }
1098 }
1099}
1100
1101impl Default for Image {
1102 fn default() -> Self {
1104 let mut image = Image::default_uninit();
1105 image.data = Some(vec![
1106 255;
1107 image
1108 .texture_descriptor
1109 .format
1110 .pixel_size()
1111 .unwrap_or(0)
1112 ]);
1113 image
1114 }
1115}
1116
1117impl Image {
1118 pub fn new(
1124 size: Extent3d,
1125 dimension: TextureDimension,
1126 data: Vec<u8>,
1127 format: TextureFormat,
1128 asset_usage: RenderAssetUsages,
1129 ) -> Self {
1130 if let Ok(pixel_size) = format.pixel_size() {
1131 debug_assert_eq!(
1132 pixel_count(size) * pixel_size,
1133 data.len(),
1134 "Pixel data, size and format have to match",
1135 );
1136 }
1137 let mut image = Image::new_uninit(size, dimension, format, asset_usage);
1138 image.data = Some(data);
1139 image
1140 }
1141
1142 pub fn new_uninit(
1144 size: Extent3d,
1145 dimension: TextureDimension,
1146 format: TextureFormat,
1147 asset_usage: RenderAssetUsages,
1148 ) -> Self {
1149 Image {
1150 data: None,
1151 data_order: TextureDataOrder::default(),
1152 texture_descriptor: TextureDescriptor {
1153 size,
1154 format,
1155 dimension,
1156 label: None,
1157 mip_level_count: 1,
1158 sample_count: 1,
1159 usage: TextureUsages::TEXTURE_BINDING
1160 | TextureUsages::COPY_DST
1161 | TextureUsages::COPY_SRC,
1162 view_formats: &[],
1163 },
1164 sampler: ImageSampler::Default,
1165 texture_view_descriptor: None,
1166 asset_usage,
1167 copy_on_resize: false,
1168 }
1169 }
1170
1171 pub fn transparent() -> Image {
1175 let format = TextureFormat::Rgba8UnormSrgb;
1179 if let Ok(pixel_size) = format.pixel_size() {
1180 debug_assert!(pixel_size == 4);
1181 }
1182 let data = vec![255, 255, 255, 0];
1183 Image::new(
1184 Extent3d::default(),
1185 TextureDimension::D2,
1186 data,
1187 format,
1188 RenderAssetUsages::default(),
1189 )
1190 }
1191 pub fn default_uninit() -> Image {
1193 Image::new_uninit(
1194 Extent3d::default(),
1195 TextureDimension::D2,
1196 TextureFormat::Rgba8UnormSrgb,
1197 RenderAssetUsages::default(),
1198 )
1199 }
1200
1201 pub fn new_fill(
1207 size: Extent3d,
1208 dimension: TextureDimension,
1209 pixel: &[u8],
1210 format: TextureFormat,
1211 asset_usage: RenderAssetUsages,
1212 ) -> Self {
1213 let mut image = Image::new_uninit(size, dimension, format, asset_usage);
1214 if let Ok(pixel_size) = image.texture_descriptor.format.pixel_size()
1215 && pixel_size > 0
1216 {
1217 let byte_len = pixel_size * pixel_count(size);
1218 debug_assert_eq!(
1219 pixel.len() % pixel_size,
1220 0,
1221 "Must not have incomplete pixel data (pixel size is {}B).",
1222 pixel_size,
1223 );
1224 debug_assert!(
1225 pixel.len() <= byte_len,
1226 "Fill data must fit within pixel buffer (expected {byte_len}B).",
1227 );
1228 let data = pixel.iter().copied().cycle().take(byte_len).collect();
1229 image.data = Some(data);
1230 }
1231 image
1232 }
1233
1234 pub fn new_target_texture(
1254 width: u32,
1255 height: u32,
1256 format: TextureFormat,
1257 view_format: Option<TextureFormat>,
1258 ) -> Self {
1259 let size = Extent3d {
1260 width,
1261 height,
1262 ..Default::default()
1263 };
1264 let usage = TextureUsages::TEXTURE_BINDING
1266 | TextureUsages::COPY_DST
1267 | TextureUsages::RENDER_ATTACHMENT;
1268 let data = vec![
1270 0;
1271 format.pixel_size().expect(
1272 "Failed to create Image: can't get pixel size for this TextureFormat"
1273 ) * pixel_count(size)
1274 ];
1275
1276 Image {
1277 data: Some(data),
1278 data_order: TextureDataOrder::default(),
1279 texture_descriptor: TextureDescriptor {
1280 size,
1281 format,
1282 dimension: TextureDimension::D2,
1283 label: None,
1284 mip_level_count: 1,
1285 sample_count: 1,
1286 usage,
1287 view_formats: match view_format {
1288 Some(_) => format.srgb_view_formats(),
1289 None => &[],
1290 },
1291 },
1292 sampler: ImageSampler::Default,
1293 texture_view_descriptor: view_format.map(|f| TextureViewDescriptor {
1294 format: Some(f),
1295 ..Default::default()
1296 }),
1297 asset_usage: RenderAssetUsages::default(),
1298 copy_on_resize: true,
1299 }
1300 }
1301
1302 #[inline]
1304 pub fn width(&self) -> u32 {
1305 self.texture_descriptor.size.width
1306 }
1307
1308 #[inline]
1310 pub fn height(&self) -> u32 {
1311 self.texture_descriptor.size.height
1312 }
1313
1314 #[inline]
1316 pub fn aspect_ratio(&self) -> AspectRatio {
1317 AspectRatio::try_from_pixels(self.width(), self.height()).expect(
1318 "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
1319 )
1320 }
1321
1322 #[inline]
1324 pub fn size_f32(&self) -> Vec2 {
1325 Vec2::new(self.width() as f32, self.height() as f32)
1326 }
1327
1328 #[inline]
1330 pub fn size(&self) -> UVec2 {
1331 UVec2::new(self.width(), self.height())
1332 }
1333
1334 pub fn resize(&mut self, size: Extent3d) {
1339 self.texture_descriptor.size = size;
1340 if let Some(ref mut data) = self.data
1341 && let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1342 {
1343 data.resize(pixel_count(size) * pixel_size, 0);
1344 }
1345 }
1346
1347 pub fn reinterpret_size(
1350 &mut self,
1351 new_size: Extent3d,
1352 ) -> Result<(), TextureReinterpretationError> {
1353 if pixel_count(new_size) != pixel_count(self.texture_descriptor.size) {
1354 return Err(TextureReinterpretationError::IncompatibleSizes {
1355 old: self.texture_descriptor.size,
1356 new: new_size,
1357 });
1358 }
1359
1360 self.texture_descriptor.size = new_size;
1361 Ok(())
1362 }
1363
1364 pub fn resize_in_place(&mut self, new_size: Extent3d) {
1369 if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size() {
1370 let old_size = self.texture_descriptor.size;
1371 let byte_len = pixel_size * pixel_count(new_size);
1372 self.texture_descriptor.size = new_size;
1373
1374 let Some(ref mut data) = self.data else {
1375 self.copy_on_resize = true;
1376 return;
1377 };
1378
1379 let mut new: Vec<u8> = vec![0; byte_len];
1380
1381 let copy_width = old_size.width.min(new_size.width) as usize;
1382 let copy_height = old_size.height.min(new_size.height) as usize;
1383 let copy_depth = old_size
1384 .depth_or_array_layers
1385 .min(new_size.depth_or_array_layers) as usize;
1386
1387 let old_row_stride = old_size.width as usize * pixel_size;
1388 let old_layer_stride = old_size.height as usize * old_row_stride;
1389
1390 let new_row_stride = new_size.width as usize * pixel_size;
1391 let new_layer_stride = new_size.height as usize * new_row_stride;
1392
1393 for z in 0..copy_depth {
1394 for y in 0..copy_height {
1395 let old_offset = z * old_layer_stride + y * old_row_stride;
1396 let new_offset = z * new_layer_stride + y * new_row_stride;
1397
1398 let old_range = (old_offset)..(old_offset + copy_width * pixel_size);
1399 let new_range = (new_offset)..(new_offset + copy_width * pixel_size);
1400
1401 new[new_range].copy_from_slice(&data[old_range]);
1402 }
1403 }
1404
1405 self.data = Some(new);
1406 }
1407 }
1408
1409 pub fn reinterpret_stacked_2d_as_array(
1417 &mut self,
1418 layers: u32,
1419 ) -> Result<(), TextureReinterpretationError> {
1420 if layers < 2 {
1422 return Err(TextureReinterpretationError::NotEnoughLayers);
1423 }
1424 if self.texture_descriptor.dimension != TextureDimension::D2 {
1425 return Err(TextureReinterpretationError::WrongDimension);
1426 }
1427 if self.texture_descriptor.size.depth_or_array_layers != 1 {
1428 return Err(TextureReinterpretationError::InvalidLayerCount);
1429 }
1430 if !self.height().is_multiple_of(layers) {
1431 return Err(TextureReinterpretationError::HeightNotDivisibleByLayers {
1432 height: self.height(),
1433 layers,
1434 });
1435 }
1436
1437 self.reinterpret_size(Extent3d {
1438 width: self.width(),
1439 height: self.height() / layers,
1440 depth_or_array_layers: layers,
1441 })?;
1442
1443 Ok(())
1444 }
1445
1446 pub fn create_stacked_array_from_2d_grid(
1455 &self,
1456 rows: u32,
1457 columns: u32,
1458 ) -> Result<Image, TextureReinterpretationError> {
1459 if rows * columns < 2 {
1462 return Err(TextureReinterpretationError::NotEnoughLayers);
1463 }
1464 if self.texture_descriptor.dimension != TextureDimension::D2 {
1466 return Err(TextureReinterpretationError::WrongDimension);
1467 }
1468 if self.texture_descriptor.size.depth_or_array_layers != 1 {
1469 return Err(TextureReinterpretationError::InvalidLayerCount);
1470 }
1471 if !self.height().is_multiple_of(rows) {
1472 return Err(
1473 TextureReinterpretationError::GridHeightNotDivisibleByTileHeight {
1474 height: self.height(),
1475 tile_count_y: rows,
1476 },
1477 );
1478 }
1479 if !self.width().is_multiple_of(columns) {
1480 return Err(
1481 TextureReinterpretationError::GridWidthNotDivisibleByTileWidth {
1482 width: self.width(),
1483 tile_count_x: columns,
1484 },
1485 );
1486 }
1487
1488 let tile_width = self.width() / columns;
1489 let tile_height = self.height() / rows;
1490 let tiles_x = columns as usize;
1491 let tiles_y = rows as usize;
1492 let image_width = self.width() as usize;
1493 let total_tiles = tiles_x * tiles_y;
1494
1495 let new_data = match &self.data {
1496 Some(pixels) => {
1497 let mut new_data: Vec<u8> = Vec::with_capacity(pixels.len());
1498
1499 let pixel_size = self
1500 .texture_descriptor
1501 .format
1502 .pixel_size()
1503 .map_err(|_| TextureReinterpretationError::InvalidTextureFormat)?;
1504
1505 let tile_height_usize = tile_height as usize;
1507 let tile_width_usize = tile_width as usize;
1508
1509 for ty in 0..tiles_y {
1510 for tx in 0..tiles_x {
1511 for row in 0..tile_height_usize {
1512 let src_row = ty * tile_height_usize + row;
1513 let src_col = tx * tile_width_usize;
1514 let src_start = (src_row * image_width + src_col) * pixel_size;
1515 let src_end = src_start + tile_width_usize * pixel_size;
1516 new_data.extend_from_slice(&pixels[src_start..src_end]);
1517 }
1518 }
1519 }
1520
1521 Some(new_data)
1522 }
1523 None => None,
1524 };
1525
1526 let new_image = Image {
1529 data: new_data,
1530 data_order: self.data_order,
1531 texture_descriptor: TextureDescriptor {
1532 size: Extent3d {
1533 width: tile_width,
1534 height: tile_height,
1535 depth_or_array_layers: total_tiles as u32,
1536 },
1537 ..self.texture_descriptor.clone()
1538 },
1539 sampler: self.sampler.clone(),
1540 texture_view_descriptor: self.texture_view_descriptor.clone(),
1541 asset_usage: self.asset_usage,
1542 copy_on_resize: self.copy_on_resize,
1543 };
1544
1545 Ok(new_image)
1546 }
1547
1548 pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
1557 self.clone()
1558 .try_into_dynamic()
1559 .ok()
1560 .filter(|img| {
1564 !matches!(
1565 img,
1566 image::DynamicImage::ImageRgb32F(_) | image::DynamicImage::ImageRgba32F(_)
1567 )
1568 })
1569 .and_then(|img| match new_format {
1570 TextureFormat::R8Unorm => {
1571 Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
1572 }
1573 TextureFormat::Rg8Unorm => Some((
1574 image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
1575 false,
1576 )),
1577 TextureFormat::Rgba8UnormSrgb => {
1578 Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
1579 }
1580 _ => None,
1581 })
1582 .map(|(dyn_img, is_srgb)| {
1583 Self::from_dynamic_inner(dyn_img, is_srgb, false, self.asset_usage)
1584 })
1585 }
1586
1587 pub fn from_buffer(
1590 buffer: &[u8],
1591 image_type: ImageType,
1592 supported_compressed_formats: CompressedImageFormats,
1593 is_srgb: bool,
1594 image_sampler: ImageSampler,
1595 asset_usage: RenderAssetUsages,
1596 ) -> Result<Image, TextureError> {
1597 Self::from_buffer_inner(
1598 buffer,
1599 image_type,
1600 supported_compressed_formats,
1601 is_srgb,
1602 true,
1603 image_sampler,
1604 asset_usage,
1605 )
1606 }
1607
1608 pub(crate) fn from_buffer_inner(
1609 buffer: &[u8],
1610 image_type: ImageType,
1611 #[cfg_attr(
1612 not(any(feature = "basis-universal", feature = "dds", feature = "ktx2")),
1613 expect(unused_variables, reason = "only used with certain features")
1614 )]
1615 supported_compressed_formats: CompressedImageFormats,
1616 is_srgb: bool,
1617 expand_grayscale: bool,
1618 image_sampler: ImageSampler,
1619 asset_usage: RenderAssetUsages,
1620 ) -> Result<Image, TextureError> {
1621 let format = image_type.to_image_format()?;
1622
1623 let mut image = match format {
1630 #[cfg(feature = "basis-universal")]
1631 ImageFormat::Basis => {
1632 basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
1633 }
1634 #[cfg(feature = "dds")]
1635 ImageFormat::Dds => dds_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?,
1636 #[cfg(feature = "ktx2")]
1637 ImageFormat::Ktx2 => {
1638 ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
1639 }
1640 #[expect(
1641 clippy::allow_attributes,
1642 reason = "`unreachable_patterns` may not always lint"
1643 )]
1644 #[allow(
1645 unreachable_patterns,
1646 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"
1647 )]
1648 _ => {
1649 let image_crate_format = format
1650 .as_image_crate_format()
1651 .ok_or_else(|| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?;
1652 let mut reader = image::ImageReader::new(std::io::Cursor::new(buffer));
1653 reader.set_format(image_crate_format);
1654 reader.no_limits();
1655 let dyn_img = reader.decode()?;
1656 Self::from_dynamic_inner(dyn_img, is_srgb, expand_grayscale, asset_usage)
1657 }
1658 };
1659 image.sampler = image_sampler;
1660 image.asset_usage = asset_usage;
1661 Ok(image)
1662 }
1663
1664 pub fn is_compressed(&self) -> bool {
1666 let format_description = self.texture_descriptor.format;
1667 format_description
1668 .required_features()
1669 .contains(Features::TEXTURE_COMPRESSION_ASTC)
1670 || format_description
1671 .required_features()
1672 .contains(Features::TEXTURE_COMPRESSION_ASTC_HDR)
1673 || format_description
1674 .required_features()
1675 .contains(Features::TEXTURE_COMPRESSION_BC)
1676 || format_description
1677 .required_features()
1678 .contains(Features::TEXTURE_COMPRESSION_ETC2)
1679 }
1680
1681 #[inline(always)]
1687 pub fn pixel_data_offset(&self, coords: UVec3) -> Result<usize, TextureAccessError> {
1688 let width = self.texture_descriptor.size.width;
1689 let height = self.texture_descriptor.size.height;
1690 let depth = self.texture_descriptor.size.depth_or_array_layers;
1691
1692 let pixel_size = self.texture_descriptor.format.pixel_size()?;
1693 let pixel_offset = match self.texture_descriptor.dimension {
1694 TextureDimension::D3 | TextureDimension::D2 => {
1695 if coords.x >= width || coords.y >= height || coords.z >= depth {
1696 return Err(TextureAccessError::OutOfBounds {
1697 x: coords.x,
1698 y: coords.y,
1699 z: coords.z,
1700 });
1701 }
1702 coords.z * height * width + coords.y * width + coords.x
1703 }
1704 TextureDimension::D1 => {
1705 if coords.x >= width {
1706 return Err(TextureAccessError::OutOfBounds {
1707 x: coords.x,
1708 y: coords.y,
1709 z: coords.z,
1710 });
1711 }
1712 coords.x
1713 }
1714 };
1715
1716 Ok(pixel_offset as usize * pixel_size)
1717 }
1718
1719 #[inline(always)]
1721 pub fn pixel_bytes(&self, coords: UVec3) -> Result<&[u8], TextureAccessError> {
1722 let len = self.texture_descriptor.format.pixel_size()?;
1723 let start = self.pixel_data_offset(coords)?;
1724 let Some(data) = self.data.as_ref() else {
1725 return Err(TextureAccessError::Uninitialized);
1726 };
1727 Ok(&data[start..(start + len)])
1728 }
1729
1730 #[inline(always)]
1732 pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Result<&mut [u8], TextureAccessError> {
1733 let len = self.texture_descriptor.format.pixel_size()?;
1734 let offset = self.pixel_data_offset(coords)?;
1735 let Some(data) = self.data.as_mut() else {
1736 return Err(TextureAccessError::Uninitialized);
1737 };
1738
1739 Ok(&mut data[offset..(offset + len)])
1740 }
1741
1742 pub fn clear(&mut self, pixel: &[u8]) {
1747 if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1748 && pixel_size > 0
1749 {
1750 let byte_len = pixel_size * pixel_count(self.texture_descriptor.size);
1751 debug_assert_eq!(
1752 pixel.len() % pixel_size,
1753 0,
1754 "Must not have incomplete pixel data (pixel size is {}B).",
1755 pixel_size,
1756 );
1757 debug_assert!(
1758 pixel.len() <= byte_len,
1759 "Clear data must fit within pixel buffer (expected {byte_len}B).",
1760 );
1761 if let Some(data) = self.data.as_mut() {
1762 for pixel_data in data.chunks_mut(pixel_size) {
1763 pixel_data.copy_from_slice(pixel);
1764 }
1765 }
1766 }
1767 }
1768
1769 #[inline(always)]
1773 pub fn get_color_at_1d(&self, x: u32) -> Result<Color, TextureAccessError> {
1774 if self.texture_descriptor.dimension != TextureDimension::D1 {
1775 return Err(TextureAccessError::WrongDimension);
1776 }
1777 self.get_color_at_internal(UVec3::new(x, 0, 0))
1778 }
1779
1780 #[inline(always)]
1804 pub fn get_color_at(&self, x: u32, y: u32) -> Result<Color, TextureAccessError> {
1805 if self.texture_descriptor.dimension != TextureDimension::D2 {
1806 return Err(TextureAccessError::WrongDimension);
1807 }
1808 self.get_color_at_internal(UVec3::new(x, y, 0))
1809 }
1810
1811 #[inline(always)]
1815 pub fn get_color_at_3d(&self, x: u32, y: u32, z: u32) -> Result<Color, TextureAccessError> {
1816 match (
1817 self.texture_descriptor.dimension,
1818 self.texture_descriptor.size.depth_or_array_layers,
1819 ) {
1820 (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1821 self.get_color_at_internal(UVec3::new(x, y, z))
1822 }
1823 _ => Err(TextureAccessError::WrongDimension),
1824 }
1825 }
1826
1827 #[inline(always)]
1831 pub fn set_color_at_1d(&mut self, x: u32, color: Color) -> Result<(), TextureAccessError> {
1832 if self.texture_descriptor.dimension != TextureDimension::D1 {
1833 return Err(TextureAccessError::WrongDimension);
1834 }
1835 self.set_color_at_internal(UVec3::new(x, 0, 0), color)
1836 }
1837
1838 #[inline(always)]
1863 pub fn set_color_at(&mut self, x: u32, y: u32, color: Color) -> Result<(), TextureAccessError> {
1864 if self.texture_descriptor.dimension != TextureDimension::D2 {
1865 return Err(TextureAccessError::WrongDimension);
1866 }
1867 self.set_color_at_internal(UVec3::new(x, y, 0), color)
1868 }
1869
1870 #[inline(always)]
1874 pub fn set_color_at_3d(
1875 &mut self,
1876 x: u32,
1877 y: u32,
1878 z: u32,
1879 color: Color,
1880 ) -> Result<(), TextureAccessError> {
1881 match (
1882 self.texture_descriptor.dimension,
1883 self.texture_descriptor.size.depth_or_array_layers,
1884 ) {
1885 (TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1886 self.set_color_at_internal(UVec3::new(x, y, z), color)
1887 }
1888 _ => Err(TextureAccessError::WrongDimension),
1889 }
1890 }
1891
1892 #[inline(always)]
1893 fn get_color_at_internal(&self, coords: UVec3) -> Result<Color, TextureAccessError> {
1894 let bytes = self.pixel_bytes(coords)?;
1895
1896 match self.texture_descriptor.format {
1899 TextureFormat::Rgba8UnormSrgb => Ok(Color::srgba(
1900 bytes[0] as f32 / u8::MAX as f32,
1901 bytes[1] as f32 / u8::MAX as f32,
1902 bytes[2] as f32 / u8::MAX as f32,
1903 bytes[3] as f32 / u8::MAX as f32,
1904 )),
1905 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => Ok(Color::linear_rgba(
1906 bytes[0] as f32 / u8::MAX as f32,
1907 bytes[1] as f32 / u8::MAX as f32,
1908 bytes[2] as f32 / u8::MAX as f32,
1909 bytes[3] as f32 / u8::MAX as f32,
1910 )),
1911 TextureFormat::Bgra8UnormSrgb => Ok(Color::srgba(
1912 bytes[2] as f32 / u8::MAX as f32,
1913 bytes[1] as f32 / u8::MAX as f32,
1914 bytes[0] as f32 / u8::MAX as f32,
1915 bytes[3] as f32 / u8::MAX as f32,
1916 )),
1917 TextureFormat::Bgra8Unorm => Ok(Color::linear_rgba(
1918 bytes[2] as f32 / u8::MAX as f32,
1919 bytes[1] as f32 / u8::MAX as f32,
1920 bytes[0] as f32 / u8::MAX as f32,
1921 bytes[3] as f32 / u8::MAX as f32,
1922 )),
1923 TextureFormat::Rgba32Float => Ok(Color::linear_rgba(
1924 f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1925 f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1926 f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1927 f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1928 )),
1929 TextureFormat::Rgba16Float => Ok(Color::linear_rgba(
1930 half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32(),
1931 half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32(),
1932 half::f16::from_le_bytes([bytes[4], bytes[5]]).to_f32(),
1933 half::f16::from_le_bytes([bytes[6], bytes[7]]).to_f32(),
1934 )),
1935 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1936 let (r, g, b, a) = (
1937 u16::from_le_bytes([bytes[0], bytes[1]]),
1938 u16::from_le_bytes([bytes[2], bytes[3]]),
1939 u16::from_le_bytes([bytes[4], bytes[5]]),
1940 u16::from_le_bytes([bytes[6], bytes[7]]),
1941 );
1942 Ok(Color::linear_rgba(
1943 (r as f64 / u16::MAX as f64) as f32,
1945 (g as f64 / u16::MAX as f64) as f32,
1946 (b as f64 / u16::MAX as f64) as f32,
1947 (a as f64 / u16::MAX as f64) as f32,
1948 ))
1949 }
1950 TextureFormat::Rgba32Uint => {
1951 let (r, g, b, a) = (
1952 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1953 u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1954 u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1955 u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1956 );
1957 Ok(Color::linear_rgba(
1958 (r as f64 / u32::MAX as f64) as f32,
1960 (g as f64 / u32::MAX as f64) as f32,
1961 (b as f64 / u32::MAX as f64) as f32,
1962 (a as f64 / u32::MAX as f64) as f32,
1963 ))
1964 }
1965 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1968 let x = bytes[0] as f32 / u8::MAX as f32;
1969 Ok(Color::linear_rgb(x, x, x))
1970 }
1971 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1972 let x = u16::from_le_bytes([bytes[0], bytes[1]]);
1973 let x = (x as f64 / u16::MAX as f64) as f32;
1975 Ok(Color::linear_rgb(x, x, x))
1976 }
1977 TextureFormat::R32Uint => {
1978 let x = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1979 let x = (x as f64 / u32::MAX as f64) as f32;
1981 Ok(Color::linear_rgb(x, x, x))
1982 }
1983 TextureFormat::R16Float => {
1984 let x = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1985 Ok(Color::linear_rgb(x, x, x))
1986 }
1987 TextureFormat::R32Float => {
1988 let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1989 Ok(Color::linear_rgb(x, x, x))
1990 }
1991 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1992 let r = bytes[0] as f32 / u8::MAX as f32;
1993 let g = bytes[1] as f32 / u8::MAX as f32;
1994 Ok(Color::linear_rgb(r, g, 0.0))
1995 }
1996 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1997 let r = u16::from_le_bytes([bytes[0], bytes[1]]);
1998 let g = u16::from_le_bytes([bytes[2], bytes[3]]);
1999 let r = (r as f64 / u16::MAX as f64) as f32;
2001 let g = (g as f64 / u16::MAX as f64) as f32;
2002 Ok(Color::linear_rgb(r, g, 0.0))
2003 }
2004 TextureFormat::Rg32Uint => {
2005 let r = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
2006 let g = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
2007 let r = (r as f64 / u32::MAX as f64) as f32;
2009 let g = (g as f64 / u32::MAX as f64) as f32;
2010 Ok(Color::linear_rgb(r, g, 0.0))
2011 }
2012 TextureFormat::Rg16Float => {
2013 let r = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
2014 let g = half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32();
2015 Ok(Color::linear_rgb(r, g, 0.0))
2016 }
2017 TextureFormat::Rg32Float => {
2018 let r = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
2019 let g = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
2020 Ok(Color::linear_rgb(r, g, 0.0))
2021 }
2022 _ => Err(TextureAccessError::UnsupportedTextureFormat(
2023 self.texture_descriptor.format,
2024 )),
2025 }
2026 }
2027
2028 #[inline(always)]
2029 fn set_color_at_internal(
2030 &mut self,
2031 coords: UVec3,
2032 color: Color,
2033 ) -> Result<(), TextureAccessError> {
2034 let format = self.texture_descriptor.format;
2035
2036 let bytes = self.pixel_bytes_mut(coords)?;
2037
2038 match format {
2041 TextureFormat::Rgba8UnormSrgb => {
2042 let [r, g, b, a] = Srgba::from(color).to_f32_array();
2043 bytes[0] = (r * u8::MAX as f32) as u8;
2044 bytes[1] = (g * u8::MAX as f32) as u8;
2045 bytes[2] = (b * u8::MAX as f32) as u8;
2046 bytes[3] = (a * u8::MAX as f32) as u8;
2047 }
2048 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => {
2049 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2050 bytes[0] = (r * u8::MAX as f32) as u8;
2051 bytes[1] = (g * u8::MAX as f32) as u8;
2052 bytes[2] = (b * u8::MAX as f32) as u8;
2053 bytes[3] = (a * u8::MAX as f32) as u8;
2054 }
2055 TextureFormat::Bgra8UnormSrgb => {
2056 let [r, g, b, a] = Srgba::from(color).to_f32_array();
2057 bytes[0] = (b * u8::MAX as f32) as u8;
2058 bytes[1] = (g * u8::MAX as f32) as u8;
2059 bytes[2] = (r * u8::MAX as f32) as u8;
2060 bytes[3] = (a * u8::MAX as f32) as u8;
2061 }
2062 TextureFormat::Bgra8Unorm => {
2063 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2064 bytes[0] = (b * u8::MAX as f32) as u8;
2065 bytes[1] = (g * u8::MAX as f32) as u8;
2066 bytes[2] = (r * u8::MAX as f32) as u8;
2067 bytes[3] = (a * u8::MAX as f32) as u8;
2068 }
2069 TextureFormat::Rgba16Float => {
2070 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2071 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
2072 bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
2073 bytes[4..6].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(b)));
2074 bytes[6..8].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(a)));
2075 }
2076 TextureFormat::Rgba32Float => {
2077 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2078 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
2079 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
2080 bytes[8..12].copy_from_slice(&f32::to_le_bytes(b));
2081 bytes[12..16].copy_from_slice(&f32::to_le_bytes(a));
2082 }
2083 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
2084 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2085 let [r, g, b, a] = [
2086 (r * u16::MAX as f32) as u16,
2087 (g * u16::MAX as f32) as u16,
2088 (b * u16::MAX as f32) as u16,
2089 (a * u16::MAX as f32) as u16,
2090 ];
2091 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
2092 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
2093 bytes[4..6].copy_from_slice(&u16::to_le_bytes(b));
2094 bytes[6..8].copy_from_slice(&u16::to_le_bytes(a));
2095 }
2096 TextureFormat::Rgba32Uint => {
2097 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
2098 let [r, g, b, a] = [
2099 (r * u32::MAX as f32) as u32,
2100 (g * u32::MAX as f32) as u32,
2101 (b * u32::MAX as f32) as u32,
2102 (a * u32::MAX as f32) as u32,
2103 ];
2104 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
2105 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
2106 bytes[8..12].copy_from_slice(&u32::to_le_bytes(b));
2107 bytes[12..16].copy_from_slice(&u32::to_le_bytes(a));
2108 }
2109 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
2110 let linear = LinearRgba::from(color);
2112 let luminance = Xyza::from(linear).y;
2113 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
2114 bytes[0] = (r * u8::MAX as f32) as u8;
2115 }
2116 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
2117 let linear = LinearRgba::from(color);
2119 let luminance = Xyza::from(linear).y;
2120 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
2121 let r = (r * u16::MAX as f32) as u16;
2122 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
2123 }
2124 TextureFormat::R32Uint => {
2125 let linear = LinearRgba::from(color);
2127 let luminance = Xyza::from(linear).y;
2128 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
2129 let r = (r as f64 * u32::MAX as f64) as u32;
2131 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
2132 }
2133 TextureFormat::R16Float => {
2134 let linear = LinearRgba::from(color);
2136 let luminance = Xyza::from(linear).y;
2137 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
2138 let x = half::f16::from_f32(r);
2139 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(x));
2140 }
2141 TextureFormat::R32Float => {
2142 let linear = LinearRgba::from(color);
2144 let luminance = Xyza::from(linear).y;
2145 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
2146 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
2147 }
2148 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
2149 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
2150 bytes[0] = (r * u8::MAX as f32) as u8;
2151 bytes[1] = (g * u8::MAX as f32) as u8;
2152 }
2153 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
2154 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
2155 let r = (r * u16::MAX as f32) as u16;
2156 let g = (g * u16::MAX as f32) as u16;
2157 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
2158 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
2159 }
2160 TextureFormat::Rg32Uint => {
2161 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
2162 let r = (r as f64 * u32::MAX as f64) as u32;
2164 let g = (g as f64 * u32::MAX as f64) as u32;
2165 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
2166 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
2167 }
2168 TextureFormat::Rg16Float => {
2169 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
2170 bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
2171 bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
2172 }
2173 TextureFormat::Rg32Float => {
2174 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
2175 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
2176 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
2177 }
2178 _ => {
2179 return Err(TextureAccessError::UnsupportedTextureFormat(
2180 self.texture_descriptor.format,
2181 ));
2182 }
2183 }
2184 Ok(())
2185 }
2186}
2187
2188#[derive(Clone, Copy, Debug)]
2192pub enum TextureChannelLayout {
2193 Rgb,
2195 Rgba,
2197 Rrr,
2199 Rrrg,
2201 Rg,
2203}
2204
2205#[derive(Clone, Copy, Debug)]
2207pub enum TranscodeFormat {
2208 Etc1s,
2210 Uastc(TextureChannelLayout),
2212 R8UnormSrgb,
2214 Rg8UnormSrgb,
2216 Rgb8,
2218}
2219
2220#[derive(Error, Debug)]
2222pub enum TextureReinterpretationError {
2223 #[error("incompatible sizes: old = {old:?} new = {new:?}")]
2225 IncompatibleSizes {
2226 old: Extent3d,
2228 new: Extent3d,
2230 },
2231 #[error("must be a 2d image")]
2233 WrongDimension,
2234 #[error("Rows * Columns must be > 1")]
2237 NotEnoughLayers,
2238 #[error("must not already be a layered image")]
2240 InvalidLayerCount,
2241 #[error("can not evenly divide height = {height} by layers = {layers}")]
2243 HeightNotDivisibleByLayers {
2244 height: u32,
2246 layers: u32,
2248 },
2249 #[error("can not evenly divide grid with height = {height} by tiles = {tile_count_y}")]
2251 GridHeightNotDivisibleByTileHeight {
2252 height: u32,
2254 tile_count_y: u32,
2256 },
2257 #[error("can not evenly divide grid with width = {width} by tiles = {tile_count_x}")]
2259 GridWidthNotDivisibleByTileWidth {
2260 width: u32,
2262 tile_count_x: u32,
2264 },
2265 #[error("Cannot process texture in its current format. Is it compressed?")]
2267 InvalidTextureFormat,
2268}
2269
2270#[derive(Error, Debug)]
2272pub enum TextureAccessError {
2273 #[error("out of bounds (x: {x}, y: {y}, z: {z})")]
2275 OutOfBounds {
2276 x: u32,
2278 y: u32,
2280 z: u32,
2282 },
2283 #[error("unsupported texture format: {0:?}")]
2287 UnsupportedTextureFormat(TextureFormat),
2288 #[error("image data is not initialized")]
2294 Uninitialized,
2295 #[error("attempt to access texture with different dimension")]
2297 WrongDimension,
2298}
2299
2300#[derive(Error, Debug)]
2302pub enum TextureError {
2303 #[error("invalid image mime type: {0}")]
2305 InvalidImageMimeType(String),
2306 #[error("invalid image extension: {0}")]
2308 InvalidImageExtension(String),
2309 #[error("failed to load an image: {0}")]
2311 ImageError(#[from] image::ImageError),
2312 #[error("unsupported texture format: {0}")]
2314 UnsupportedTextureFormat(String),
2315 #[error("supercompression not supported: {0}")]
2317 SuperCompressionNotSupported(String),
2318 #[error("failed to decompress an image: {0}")]
2320 SuperDecompressionError(String),
2321 #[error("invalid data: {0}")]
2323 InvalidData(String),
2324 #[error("transcode error: {0}")]
2326 TranscodeError(String),
2327 #[error("format requires transcoding: {0:?}")]
2329 FormatRequiresTranscodingError(TranscodeFormat),
2330 #[error("only cubemaps with six faces are supported")]
2332 IncompleteCubemap,
2333}
2334
2335#[derive(Debug)]
2337pub enum ImageType<'a> {
2338 MimeType(&'a str),
2340 Extension(&'a str),
2342 Format(ImageFormat),
2344}
2345
2346impl<'a> ImageType<'a> {
2347 pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
2354 match self {
2355 ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
2356 .ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
2357 ImageType::Extension(extension) => ImageFormat::from_extension(extension)
2358 .ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
2359 ImageType::Format(format) => Ok(*format),
2360 }
2361 }
2362}
2363
2364fn pixel_count(item: Extent3d) -> usize {
2366 (item.width * item.height * item.depth_or_array_layers) as usize
2367}
2368
2369pub trait TextureFormatPixelInfo {
2371 fn pixel_size(&self) -> Result<usize, TextureAccessError>;
2374}
2375
2376impl TextureFormatPixelInfo for TextureFormat {
2377 fn pixel_size(&self) -> Result<usize, TextureAccessError> {
2378 let info = self;
2379 match info.block_dimensions() {
2380 (1, 1) => Ok(info.block_copy_size(None).unwrap() as usize),
2381 _ => Err(TextureAccessError::UnsupportedTextureFormat(*self)),
2382 }
2383 }
2384}
2385
2386bitflags::bitflags! {
2387 #[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
2389 #[repr(transparent)]
2390 pub struct CompressedImageFormats: u32 {
2391 const NONE = 0;
2393 const ASTC_LDR = 1 << 0;
2401 const ASTC_HDR = 1 << 1;
2406 const BC = 1 << 2;
2418 const ETC2 = 1 << 3;
2426 }
2427}
2428
2429impl CompressedImageFormats {
2430 pub fn from_features(features: Features) -> Self {
2432 let mut supported_compressed_formats = Self::default();
2433 if features.contains(Features::TEXTURE_COMPRESSION_ASTC) {
2434 supported_compressed_formats |= Self::ASTC_LDR;
2435 }
2436 if features.contains(Features::TEXTURE_COMPRESSION_ASTC_HDR) {
2437 supported_compressed_formats |= Self::ASTC_HDR;
2438 }
2439 if features.contains(Features::TEXTURE_COMPRESSION_BC) {
2440 supported_compressed_formats |= Self::BC;
2441 }
2442 if features.contains(Features::TEXTURE_COMPRESSION_ETC2) {
2443 supported_compressed_formats |= Self::ETC2;
2444 }
2445 supported_compressed_formats
2446 }
2447
2448 pub fn supports(&self, format: TextureFormat) -> bool {
2453 match format {
2454 TextureFormat::Bc1RgbaUnorm
2455 | TextureFormat::Bc1RgbaUnormSrgb
2456 | TextureFormat::Bc2RgbaUnorm
2457 | TextureFormat::Bc2RgbaUnormSrgb
2458 | TextureFormat::Bc3RgbaUnorm
2459 | TextureFormat::Bc3RgbaUnormSrgb
2460 | TextureFormat::Bc4RUnorm
2461 | TextureFormat::Bc4RSnorm
2462 | TextureFormat::Bc5RgUnorm
2463 | TextureFormat::Bc5RgSnorm
2464 | TextureFormat::Bc6hRgbUfloat
2465 | TextureFormat::Bc6hRgbFloat
2466 | TextureFormat::Bc7RgbaUnorm
2467 | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
2468 TextureFormat::Etc2Rgb8Unorm
2469 | TextureFormat::Etc2Rgb8UnormSrgb
2470 | TextureFormat::Etc2Rgb8A1Unorm
2471 | TextureFormat::Etc2Rgb8A1UnormSrgb
2472 | TextureFormat::Etc2Rgba8Unorm
2473 | TextureFormat::Etc2Rgba8UnormSrgb
2474 | TextureFormat::EacR11Unorm
2475 | TextureFormat::EacR11Snorm
2476 | TextureFormat::EacRg11Unorm
2477 | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
2478 TextureFormat::Astc {
2479 channel: wgpu_types::AstcChannel::Hdr,
2480 ..
2481 } => self.contains(CompressedImageFormats::ASTC_HDR),
2482 TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
2483 _ => true,
2484 }
2485 }
2486}
2487
2488#[derive(Resource)]
2492pub struct CompressedImageFormatSupport(pub CompressedImageFormats);
2493
2494#[cfg(test)]
2495mod test {
2496 use super::*;
2497
2498 #[test]
2499 fn convert_keeps_single_channel() {
2500 let image = Image::new_fill(
2501 Extent3d {
2502 width: 2,
2503 height: 2,
2504 depth_or_array_layers: 1,
2505 },
2506 TextureDimension::D2,
2507 &[10, 20, 30, 255],
2508 TextureFormat::Rgba8UnormSrgb,
2509 RenderAssetUsages::MAIN_WORLD,
2510 );
2511
2512 let r8 = image.convert(TextureFormat::R8Unorm).unwrap();
2513 assert_eq!(r8.texture_descriptor.format, TextureFormat::R8Unorm);
2514 assert_eq!(r8.data.as_ref().unwrap().len(), 4);
2515
2516 let rg8 = image.convert(TextureFormat::Rg8Unorm).unwrap();
2517 assert_eq!(rg8.texture_descriptor.format, TextureFormat::Rg8Unorm);
2518 assert_eq!(rg8.data.as_ref().unwrap().len(), 8);
2519 }
2520
2521 #[test]
2522 fn image_size() {
2523 let size = Extent3d {
2524 width: 200,
2525 height: 100,
2526 depth_or_array_layers: 1,
2527 };
2528 let image = Image::new_fill(
2529 size,
2530 TextureDimension::D2,
2531 &[0, 0, 0, 255],
2532 TextureFormat::Rgba8Unorm,
2533 RenderAssetUsages::MAIN_WORLD,
2534 );
2535 assert_eq!(
2536 Vec2::new(size.width as f32, size.height as f32),
2537 image.size_f32()
2538 );
2539 }
2540
2541 #[test]
2542 fn image_default_size() {
2543 let image = Image::default();
2544 assert_eq!(UVec2::ONE, image.size());
2545 assert_eq!(Vec2::ONE, image.size_f32());
2546 }
2547
2548 #[test]
2549 fn on_edge_pixel_is_invalid() {
2550 let image = Image::new_fill(
2551 Extent3d {
2552 width: 5,
2553 height: 10,
2554 depth_or_array_layers: 1,
2555 },
2556 TextureDimension::D2,
2557 &[0, 0, 0, 255],
2558 TextureFormat::Rgba8Unorm,
2559 RenderAssetUsages::MAIN_WORLD,
2560 );
2561 assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK)));
2562 assert!(matches!(
2563 image.get_color_at(0, 10),
2564 Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 })
2565 ));
2566 assert!(matches!(
2567 image.get_color_at(5, 10),
2568 Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 })
2569 ));
2570 }
2571
2572 #[test]
2573 fn compressed_texture_format_is_reported_correctly() {
2574 let mut image = Image::new_uninit(
2575 Extent3d {
2576 width: 4,
2577 height: 4,
2578 depth_or_array_layers: 1,
2579 },
2580 TextureDimension::D2,
2581 TextureFormat::Bc1RgbaUnorm,
2582 RenderAssetUsages::MAIN_WORLD,
2583 );
2584
2585 assert!(matches!(
2586 image.get_color_at(0, 0),
2587 Err(TextureAccessError::UnsupportedTextureFormat(
2588 TextureFormat::Bc1RgbaUnorm
2589 ))
2590 ));
2591
2592 assert!(matches!(
2593 image.set_color_at(0, 0, Color::WHITE),
2594 Err(TextureAccessError::UnsupportedTextureFormat(
2595 TextureFormat::Bc1RgbaUnorm
2596 ))
2597 ));
2598 }
2599
2600 #[test]
2601 fn get_set_pixel_2d_with_layers() {
2602 let mut image = Image::new_fill(
2603 Extent3d {
2604 width: 5,
2605 height: 10,
2606 depth_or_array_layers: 3,
2607 },
2608 TextureDimension::D2,
2609 &[0, 0, 0, 255],
2610 TextureFormat::Rgba8Unorm,
2611 RenderAssetUsages::MAIN_WORLD,
2612 );
2613 image.set_color_at_3d(0, 0, 0, Color::WHITE).unwrap();
2614 assert!(matches!(image.get_color_at_3d(0, 0, 0), Ok(Color::WHITE)));
2615 image.set_color_at_3d(2, 3, 1, Color::WHITE).unwrap();
2616 assert!(matches!(image.get_color_at_3d(2, 3, 1), Ok(Color::WHITE)));
2617 image.set_color_at_3d(4, 9, 2, Color::WHITE).unwrap();
2618 assert!(matches!(image.get_color_at_3d(4, 9, 2), Ok(Color::WHITE)));
2619 }
2620
2621 #[test]
2622 fn resize_in_place_2d_grow_and_shrink() {
2623 use bevy_color::ColorToPacked;
2624
2625 const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2626 const GROW_FILL: LinearRgba = LinearRgba::NONE;
2627
2628 let mut image = Image::new_fill(
2629 Extent3d {
2630 width: 2,
2631 height: 2,
2632 depth_or_array_layers: 1,
2633 },
2634 TextureDimension::D2,
2635 &INITIAL_FILL.to_u8_array(),
2636 TextureFormat::Rgba8Unorm,
2637 RenderAssetUsages::MAIN_WORLD,
2638 );
2639
2640 const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2643 (0, 1, LinearRgba::RED),
2644 (1, 1, LinearRgba::GREEN),
2645 (1, 0, LinearRgba::BLUE),
2646 ];
2647
2648 for (x, y, color) in &TEST_PIXELS {
2649 image.set_color_at(*x, *y, Color::from(*color)).unwrap();
2650 }
2651
2652 image.resize_in_place(Extent3d {
2654 width: 4,
2655 height: 4,
2656 depth_or_array_layers: 1,
2657 });
2658
2659 assert!(matches!(
2661 image.get_color_at(0, 0),
2662 Ok(Color::LinearRgba(INITIAL_FILL))
2663 ));
2664 for (x, y, color) in &TEST_PIXELS {
2665 assert_eq!(
2666 image.get_color_at(*x, *y).unwrap(),
2667 Color::LinearRgba(*color)
2668 );
2669 }
2670
2671 assert!(matches!(
2673 image.get_color_at(3, 3),
2674 Ok(Color::LinearRgba(GROW_FILL))
2675 ));
2676
2677 image.resize_in_place(Extent3d {
2679 width: 1,
2680 height: 1,
2681 depth_or_array_layers: 1,
2682 });
2683
2684 assert!(image.get_color_at(1, 1).is_err());
2686 }
2687
2688 #[test]
2689 fn resize_in_place_array_grow_and_shrink() {
2690 use bevy_color::ColorToPacked;
2691
2692 const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2693 const GROW_FILL: LinearRgba = LinearRgba::NONE;
2694 const LAYERS: u32 = 4;
2695
2696 let mut image = Image::new_fill(
2697 Extent3d {
2698 width: 2,
2699 height: 2,
2700 depth_or_array_layers: LAYERS,
2701 },
2702 TextureDimension::D2,
2703 &INITIAL_FILL.to_u8_array(),
2704 TextureFormat::Rgba8Unorm,
2705 RenderAssetUsages::MAIN_WORLD,
2706 );
2707
2708 const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2711 (0, 1, LinearRgba::RED),
2712 (1, 1, LinearRgba::GREEN),
2713 (1, 0, LinearRgba::BLUE),
2714 ];
2715
2716 for z in 0..LAYERS {
2717 for (x, y, color) in &TEST_PIXELS {
2718 image
2719 .set_color_at_3d(*x, *y, z, Color::from(*color))
2720 .unwrap();
2721 }
2722 }
2723
2724 image.resize_in_place(Extent3d {
2726 width: 4,
2727 height: 4,
2728 depth_or_array_layers: LAYERS + 1,
2729 });
2730
2731 assert!(matches!(
2733 image.get_color_at(0, 0),
2734 Ok(Color::LinearRgba(INITIAL_FILL))
2735 ));
2736 for z in 0..LAYERS {
2737 for (x, y, color) in &TEST_PIXELS {
2738 assert_eq!(
2739 image.get_color_at_3d(*x, *y, z).unwrap(),
2740 Color::LinearRgba(*color)
2741 );
2742 }
2743 }
2744
2745 for z in 0..(LAYERS + 1) {
2747 assert!(matches!(
2748 image.get_color_at_3d(3, 3, z),
2749 Ok(Color::LinearRgba(GROW_FILL))
2750 ));
2751 }
2752
2753 image.resize_in_place(Extent3d {
2755 width: 1,
2756 height: 1,
2757 depth_or_array_layers: 1,
2758 });
2759
2760 assert!(image.get_color_at_3d(1, 1, 0).is_err());
2762
2763 assert!(image.get_color_at_3d(0, 0, 1).is_err());
2765
2766 image.resize_in_place(Extent3d {
2768 width: 1,
2769 height: 1,
2770 depth_or_array_layers: 2,
2771 });
2772
2773 assert!(matches!(
2775 image.get_color_at_3d(0, 0, 1),
2776 Ok(Color::LinearRgba(GROW_FILL))
2777 ));
2778 }
2779
2780 #[test]
2781 fn create_array_from_2d_grid() {
2782 let image = Image::new_fill(
2784 Extent3d {
2785 width: 6,
2786 height: 6,
2787 depth_or_array_layers: 1,
2788 },
2789 TextureDimension::D2,
2790 &[0; 4],
2791 TextureFormat::Rgba8Snorm,
2792 RenderAssetUsages::all(),
2793 );
2794
2795 assert!(image.texture_descriptor.size.depth_or_array_layers == 1);
2796
2797 let new_array_image = image.create_stacked_array_from_2d_grid(2, 2).unwrap();
2798
2799 assert!(
2801 new_array_image
2802 .texture_descriptor
2803 .size
2804 .depth_or_array_layers
2805 == 2 * 2
2806 );
2807 assert!(
2808 new_array_image.data.unwrap().len()
2809 == pixel_count(new_array_image.texture_descriptor.size)
2810 * new_array_image
2811 .texture_descriptor
2812 .format
2813 .pixel_size()
2814 .unwrap()
2815 );
2816
2817 let image = Image::new_fill(
2819 Extent3d {
2820 width: 9,
2821 height: 2,
2822 depth_or_array_layers: 1,
2823 },
2824 TextureDimension::D2,
2825 &[0; 4],
2826 TextureFormat::Rgba8Snorm,
2827 RenderAssetUsages::all(),
2828 );
2829
2830 assert!(image.texture_descriptor.size.depth_or_array_layers == 1);
2831
2832 let new_array_image = image.create_stacked_array_from_2d_grid(1, 3).unwrap();
2833
2834 assert!(
2835 new_array_image
2836 .texture_descriptor
2837 .size
2838 .depth_or_array_layers
2839 == 3
2840 );
2841 assert!(
2842 new_array_image.data.unwrap().len()
2843 == pixel_count(new_array_image.texture_descriptor.size)
2844 * new_array_image
2845 .texture_descriptor
2846 .format
2847 .pixel_size()
2848 .unwrap()
2849 );
2850
2851 let image = Image::new_fill(
2852 Extent3d {
2853 width: 2,
2854 height: 1,
2855 depth_or_array_layers: 1,
2856 },
2857 TextureDimension::D2,
2858 &[0; 4],
2859 TextureFormat::Rgba8Snorm,
2860 RenderAssetUsages::all(),
2861 );
2862
2863 assert!(image.texture_descriptor.size.depth_or_array_layers == 1);
2864
2865 let new_array_image = image.create_stacked_array_from_2d_grid(1, 2).unwrap();
2866
2867 assert!(
2868 new_array_image
2869 .texture_descriptor
2870 .size
2871 .depth_or_array_layers
2872 == 2
2873 );
2874 assert!(
2875 new_array_image.data.unwrap().len()
2876 == pixel_count(new_array_image.texture_descriptor.size)
2877 * new_array_image
2878 .texture_descriptor
2879 .format
2880 .pixel_size()
2881 .unwrap()
2882 );
2883 }
2884
2885 #[test]
2886 fn image_clear() {
2887 let mut image = Image::new_fill(
2888 Extent3d {
2889 width: 32,
2890 height: 32,
2891 depth_or_array_layers: 1,
2892 },
2893 TextureDimension::D2,
2894 &[0; 4],
2895 TextureFormat::Rgba8Snorm,
2896 RenderAssetUsages::all(),
2897 );
2898
2899 assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 0));
2900
2901 image.clear(&[255; 4]);
2902
2903 assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 255));
2904 }
2905
2906 #[test]
2907 fn get_or_init_sampler_modifications() {
2908 let mut default_sampler = ImageSampler::Default;
2910 let my_sampler_in_a_loader = default_sampler
2912 .get_or_init_descriptor()
2913 .set_filter(ImageFilterMode::Linear)
2914 .set_address_mode(ImageAddressMode::Repeat);
2915
2916 assert_eq!(
2917 my_sampler_in_a_loader.address_mode_u,
2918 ImageAddressMode::Repeat
2919 );
2920 assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2921 }
2922
2923 #[test]
2924 fn get_or_init_sampler_anisotropy() {
2925 let mut default_sampler = ImageSampler::Default;
2927 let my_sampler_in_a_loader = default_sampler
2929 .get_or_init_descriptor()
2930 .set_anisotropic_filter(8);
2931
2932 assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2933 assert_eq!(my_sampler_in_a_loader.anisotropy_clamp, 8);
2934 }
2935}