Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_image/src/image.rs
9353 views
1
use crate::ImageLoader;
2
3
#[cfg(feature = "basis-universal")]
4
use super::basis::*;
5
#[cfg(feature = "dds")]
6
use super::dds::*;
7
#[cfg(feature = "ktx2")]
8
use super::ktx2::*;
9
use bevy_app::{App, Plugin};
10
#[cfg(not(feature = "bevy_reflect"))]
11
use bevy_reflect::TypePath;
12
#[cfg(feature = "bevy_reflect")]
13
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
14
15
use bevy_asset::{uuid_handle, Asset, AssetApp, Assets, Handle, RenderAssetUsages};
16
use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza};
17
use bevy_ecs::resource::Resource;
18
use bevy_math::{AspectRatio, UVec2, UVec3, Vec2};
19
use core::hash::Hash;
20
use serde::{Deserialize, Serialize};
21
use thiserror::Error;
22
use wgpu_types::{
23
AddressMode, CompareFunction, Extent3d, Features, FilterMode, MipmapFilterMode,
24
SamplerBorderColor, SamplerDescriptor, TextureDataOrder, TextureDescriptor, TextureDimension,
25
TextureFormat, TextureUsages, TextureViewDescriptor,
26
};
27
28
/// Trait used to provide default values for Bevy-external types that
29
/// do not implement [`Default`].
30
pub trait BevyDefault {
31
/// Returns the default value for a type.
32
fn bevy_default() -> Self;
33
}
34
35
impl BevyDefault for TextureFormat {
36
fn bevy_default() -> Self {
37
TextureFormat::Rgba8UnormSrgb
38
}
39
}
40
41
/// Trait used to provide texture srgb view formats with static lifetime for `TextureDescriptor.view_formats`.
42
pub trait TextureSrgbViewFormats {
43
/// Returns the srgb view formats for a type.
44
fn srgb_view_formats(&self) -> &'static [TextureFormat];
45
}
46
47
impl TextureSrgbViewFormats for TextureFormat {
48
/// Returns the srgb view formats if the format has an srgb variant, otherwise returns an empty slice.
49
///
50
/// The return result covers all the results of [`TextureFormat::add_srgb_suffix`](wgpu_types::TextureFormat::add_srgb_suffix).
51
fn srgb_view_formats(&self) -> &'static [TextureFormat] {
52
match self {
53
TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
54
TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb],
55
TextureFormat::Bc1RgbaUnorm => &[TextureFormat::Bc1RgbaUnormSrgb],
56
TextureFormat::Bc2RgbaUnorm => &[TextureFormat::Bc2RgbaUnormSrgb],
57
TextureFormat::Bc3RgbaUnorm => &[TextureFormat::Bc3RgbaUnormSrgb],
58
TextureFormat::Bc7RgbaUnorm => &[TextureFormat::Bc7RgbaUnormSrgb],
59
TextureFormat::Etc2Rgb8Unorm => &[TextureFormat::Etc2Rgb8UnormSrgb],
60
TextureFormat::Etc2Rgb8A1Unorm => &[TextureFormat::Etc2Rgb8A1UnormSrgb],
61
TextureFormat::Etc2Rgba8Unorm => &[TextureFormat::Etc2Rgba8UnormSrgb],
62
TextureFormat::Astc {
63
block: wgpu_types::AstcBlock::B4x4,
64
channel: wgpu_types::AstcChannel::Unorm,
65
} => &[TextureFormat::Astc {
66
block: wgpu_types::AstcBlock::B4x4,
67
channel: wgpu_types::AstcChannel::UnormSrgb,
68
}],
69
TextureFormat::Astc {
70
block: wgpu_types::AstcBlock::B5x4,
71
channel: wgpu_types::AstcChannel::Unorm,
72
} => &[TextureFormat::Astc {
73
block: wgpu_types::AstcBlock::B5x4,
74
channel: wgpu_types::AstcChannel::UnormSrgb,
75
}],
76
TextureFormat::Astc {
77
block: wgpu_types::AstcBlock::B5x5,
78
channel: wgpu_types::AstcChannel::Unorm,
79
} => &[TextureFormat::Astc {
80
block: wgpu_types::AstcBlock::B5x5,
81
channel: wgpu_types::AstcChannel::UnormSrgb,
82
}],
83
TextureFormat::Astc {
84
block: wgpu_types::AstcBlock::B6x5,
85
channel: wgpu_types::AstcChannel::Unorm,
86
} => &[TextureFormat::Astc {
87
block: wgpu_types::AstcBlock::B6x5,
88
channel: wgpu_types::AstcChannel::UnormSrgb,
89
}],
90
TextureFormat::Astc {
91
block: wgpu_types::AstcBlock::B6x6,
92
channel: wgpu_types::AstcChannel::Unorm,
93
} => &[TextureFormat::Astc {
94
block: wgpu_types::AstcBlock::B6x6,
95
channel: wgpu_types::AstcChannel::UnormSrgb,
96
}],
97
TextureFormat::Astc {
98
block: wgpu_types::AstcBlock::B8x5,
99
channel: wgpu_types::AstcChannel::Unorm,
100
} => &[TextureFormat::Astc {
101
block: wgpu_types::AstcBlock::B8x5,
102
channel: wgpu_types::AstcChannel::UnormSrgb,
103
}],
104
TextureFormat::Astc {
105
block: wgpu_types::AstcBlock::B8x6,
106
channel: wgpu_types::AstcChannel::Unorm,
107
} => &[TextureFormat::Astc {
108
block: wgpu_types::AstcBlock::B8x6,
109
channel: wgpu_types::AstcChannel::UnormSrgb,
110
}],
111
TextureFormat::Astc {
112
block: wgpu_types::AstcBlock::B8x8,
113
channel: wgpu_types::AstcChannel::Unorm,
114
} => &[TextureFormat::Astc {
115
block: wgpu_types::AstcBlock::B8x8,
116
channel: wgpu_types::AstcChannel::UnormSrgb,
117
}],
118
TextureFormat::Astc {
119
block: wgpu_types::AstcBlock::B10x5,
120
channel: wgpu_types::AstcChannel::Unorm,
121
} => &[TextureFormat::Astc {
122
block: wgpu_types::AstcBlock::B10x5,
123
channel: wgpu_types::AstcChannel::UnormSrgb,
124
}],
125
TextureFormat::Astc {
126
block: wgpu_types::AstcBlock::B10x6,
127
channel: wgpu_types::AstcChannel::Unorm,
128
} => &[TextureFormat::Astc {
129
block: wgpu_types::AstcBlock::B10x6,
130
channel: wgpu_types::AstcChannel::UnormSrgb,
131
}],
132
TextureFormat::Astc {
133
block: wgpu_types::AstcBlock::B10x8,
134
channel: wgpu_types::AstcChannel::Unorm,
135
} => &[TextureFormat::Astc {
136
block: wgpu_types::AstcBlock::B10x8,
137
channel: wgpu_types::AstcChannel::UnormSrgb,
138
}],
139
TextureFormat::Astc {
140
block: wgpu_types::AstcBlock::B10x10,
141
channel: wgpu_types::AstcChannel::Unorm,
142
} => &[TextureFormat::Astc {
143
block: wgpu_types::AstcBlock::B10x10,
144
channel: wgpu_types::AstcChannel::UnormSrgb,
145
}],
146
TextureFormat::Astc {
147
block: wgpu_types::AstcBlock::B12x10,
148
channel: wgpu_types::AstcChannel::Unorm,
149
} => &[TextureFormat::Astc {
150
block: wgpu_types::AstcBlock::B12x10,
151
channel: wgpu_types::AstcChannel::UnormSrgb,
152
}],
153
TextureFormat::Astc {
154
block: wgpu_types::AstcBlock::B12x12,
155
channel: wgpu_types::AstcChannel::Unorm,
156
} => &[TextureFormat::Astc {
157
block: wgpu_types::AstcBlock::B12x12,
158
channel: wgpu_types::AstcChannel::UnormSrgb,
159
}],
160
_ => &[],
161
}
162
}
163
}
164
165
/// A handle to a 1 x 1 transparent white image.
166
///
167
/// Like [`Handle<Image>::default`], this is a handle to a fallback image asset.
168
/// While that handle points to an opaque white 1 x 1 image, this handle points to a transparent 1 x 1 white image.
169
// Number randomly selected by fair WolframAlpha query. Totally arbitrary.
170
pub const TRANSPARENT_IMAGE_HANDLE: Handle<Image> =
171
uuid_handle!("d18ad97e-a322-4981-9505-44c59a4b5e46");
172
173
/// Adds the [`Image`] as an asset and makes sure that they are extracted and prepared for the GPU.
174
pub struct ImagePlugin {
175
/// The default image sampler to use when [`ImageSampler`] is set to `Default`.
176
pub default_sampler: ImageSamplerDescriptor,
177
}
178
179
impl Default for ImagePlugin {
180
fn default() -> Self {
181
ImagePlugin::default_linear()
182
}
183
}
184
185
impl ImagePlugin {
186
/// Creates image settings with linear sampling by default.
187
pub fn default_linear() -> ImagePlugin {
188
ImagePlugin {
189
default_sampler: ImageSamplerDescriptor::linear(),
190
}
191
}
192
193
/// Creates image settings with nearest sampling by default.
194
pub fn default_nearest() -> ImagePlugin {
195
ImagePlugin {
196
default_sampler: ImageSamplerDescriptor::nearest(),
197
}
198
}
199
}
200
201
impl Plugin for ImagePlugin {
202
fn build(&self, app: &mut App) {
203
#[cfg(feature = "exr")]
204
app.init_asset_loader::<crate::ExrTextureLoader>();
205
206
#[cfg(feature = "hdr")]
207
app.init_asset_loader::<crate::HdrTextureLoader>();
208
209
app.init_asset::<Image>();
210
#[cfg(feature = "bevy_reflect")]
211
app.register_asset_reflect::<Image>();
212
213
let mut image_assets = app.world_mut().resource_mut::<Assets<Image>>();
214
215
image_assets
216
.insert(&Handle::default(), Image::default())
217
.unwrap();
218
image_assets
219
.insert(&TRANSPARENT_IMAGE_HANDLE, Image::transparent())
220
.unwrap();
221
222
#[cfg(feature = "compressed_image_saver")]
223
if let Some(processor) = app
224
.world()
225
.get_resource::<bevy_asset::processor::AssetProcessor>()
226
{
227
processor.register_processor::<bevy_asset::processor::LoadTransformAndSave<
228
ImageLoader,
229
bevy_asset::transformer::IdentityAssetTransformer<Image>,
230
crate::CompressedImageSaver,
231
>>(crate::CompressedImageSaver.into());
232
processor.set_default_processor::<bevy_asset::processor::LoadTransformAndSave<
233
ImageLoader,
234
bevy_asset::transformer::IdentityAssetTransformer<Image>,
235
crate::CompressedImageSaver,
236
>>("png");
237
}
238
239
app.preregister_asset_loader::<ImageLoader>(ImageLoader::SUPPORTED_FILE_EXTENSIONS);
240
}
241
}
242
243
pub const TEXTURE_ASSET_INDEX: u64 = 0;
244
pub const SAMPLER_ASSET_INDEX: u64 = 1;
245
246
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
247
pub enum ImageFormat {
248
#[cfg(feature = "basis-universal")]
249
Basis,
250
#[cfg(feature = "bmp")]
251
Bmp,
252
#[cfg(feature = "dds")]
253
Dds,
254
#[cfg(feature = "ff")]
255
Farbfeld,
256
#[cfg(feature = "gif")]
257
Gif,
258
#[cfg(feature = "exr")]
259
OpenExr,
260
#[cfg(feature = "hdr")]
261
Hdr,
262
#[cfg(feature = "ico")]
263
Ico,
264
#[cfg(feature = "jpeg")]
265
Jpeg,
266
#[cfg(feature = "ktx2")]
267
Ktx2,
268
#[cfg(feature = "png")]
269
Png,
270
#[cfg(feature = "pnm")]
271
Pnm,
272
#[cfg(feature = "qoi")]
273
Qoi,
274
#[cfg(feature = "tga")]
275
Tga,
276
#[cfg(feature = "tiff")]
277
Tiff,
278
#[cfg(feature = "webp")]
279
WebP,
280
}
281
282
macro_rules! feature_gate {
283
($feature: tt, $value: ident) => {{
284
#[cfg(not(feature = $feature))]
285
{
286
tracing::warn!("feature \"{}\" is not enabled", $feature);
287
return None;
288
}
289
#[cfg(feature = $feature)]
290
ImageFormat::$value
291
}};
292
}
293
294
impl ImageFormat {
295
/// Gets the file extensions for a given format.
296
pub const fn to_file_extensions(&self) -> &'static [&'static str] {
297
match self {
298
#[cfg(feature = "basis-universal")]
299
ImageFormat::Basis => &["basis"],
300
#[cfg(feature = "bmp")]
301
ImageFormat::Bmp => &["bmp"],
302
#[cfg(feature = "dds")]
303
ImageFormat::Dds => &["dds"],
304
#[cfg(feature = "ff")]
305
ImageFormat::Farbfeld => &["ff", "farbfeld"],
306
#[cfg(feature = "gif")]
307
ImageFormat::Gif => &["gif"],
308
#[cfg(feature = "exr")]
309
ImageFormat::OpenExr => &["exr"],
310
#[cfg(feature = "hdr")]
311
ImageFormat::Hdr => &["hdr"],
312
#[cfg(feature = "ico")]
313
ImageFormat::Ico => &["ico"],
314
#[cfg(feature = "jpeg")]
315
ImageFormat::Jpeg => &["jpg", "jpeg"],
316
#[cfg(feature = "ktx2")]
317
ImageFormat::Ktx2 => &["ktx2"],
318
#[cfg(feature = "pnm")]
319
ImageFormat::Pnm => &["pam", "pbm", "pgm", "ppm"],
320
#[cfg(feature = "png")]
321
ImageFormat::Png => &["png"],
322
#[cfg(feature = "qoi")]
323
ImageFormat::Qoi => &["qoi"],
324
#[cfg(feature = "tga")]
325
ImageFormat::Tga => &["tga"],
326
#[cfg(feature = "tiff")]
327
ImageFormat::Tiff => &["tif", "tiff"],
328
#[cfg(feature = "webp")]
329
ImageFormat::WebP => &["webp"],
330
// FIXME: https://github.com/rust-lang/rust/issues/129031
331
#[expect(
332
clippy::allow_attributes,
333
reason = "`unreachable_patterns` may not always lint"
334
)]
335
#[allow(
336
unreachable_patterns,
337
reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
338
)]
339
_ => &[],
340
}
341
}
342
343
/// Gets the MIME types for a given format.
344
///
345
/// If a format doesn't have any dedicated MIME types, this list will be empty.
346
pub const fn to_mime_types(&self) -> &'static [&'static str] {
347
match self {
348
#[cfg(feature = "basis-universal")]
349
ImageFormat::Basis => &["image/basis", "image/x-basis"],
350
#[cfg(feature = "bmp")]
351
ImageFormat::Bmp => &["image/bmp", "image/x-bmp"],
352
#[cfg(feature = "dds")]
353
ImageFormat::Dds => &["image/vnd-ms.dds"],
354
#[cfg(feature = "hdr")]
355
ImageFormat::Hdr => &["image/vnd.radiance"],
356
#[cfg(feature = "gif")]
357
ImageFormat::Gif => &["image/gif"],
358
#[cfg(feature = "ff")]
359
ImageFormat::Farbfeld => &[],
360
#[cfg(feature = "ico")]
361
ImageFormat::Ico => &["image/x-icon"],
362
#[cfg(feature = "jpeg")]
363
ImageFormat::Jpeg => &["image/jpeg"],
364
#[cfg(feature = "ktx2")]
365
ImageFormat::Ktx2 => &["image/ktx2"],
366
#[cfg(feature = "png")]
367
ImageFormat::Png => &["image/png"],
368
#[cfg(feature = "qoi")]
369
ImageFormat::Qoi => &["image/qoi", "image/x-qoi"],
370
#[cfg(feature = "exr")]
371
ImageFormat::OpenExr => &["image/x-exr"],
372
#[cfg(feature = "pnm")]
373
ImageFormat::Pnm => &[
374
"image/x-portable-bitmap",
375
"image/x-portable-graymap",
376
"image/x-portable-pixmap",
377
"image/x-portable-anymap",
378
],
379
#[cfg(feature = "tga")]
380
ImageFormat::Tga => &["image/x-targa", "image/x-tga"],
381
#[cfg(feature = "tiff")]
382
ImageFormat::Tiff => &["image/tiff"],
383
#[cfg(feature = "webp")]
384
ImageFormat::WebP => &["image/webp"],
385
// FIXME: https://github.com/rust-lang/rust/issues/129031
386
#[expect(
387
clippy::allow_attributes,
388
reason = "`unreachable_patterns` may not always lint"
389
)]
390
#[allow(
391
unreachable_patterns,
392
reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
393
)]
394
_ => &[],
395
}
396
}
397
398
pub fn from_mime_type(mime_type: &str) -> Option<Self> {
399
#[expect(
400
clippy::allow_attributes,
401
reason = "`unreachable_code` may not always lint"
402
)]
403
#[allow(
404
unreachable_code,
405
reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
406
)]
407
Some(match mime_type.to_ascii_lowercase().as_str() {
408
// note: farbfeld does not have a MIME type
409
"image/basis" | "image/x-basis" => feature_gate!("basis-universal", Basis),
410
"image/bmp" | "image/x-bmp" => feature_gate!("bmp", Bmp),
411
"image/vnd-ms.dds" => feature_gate!("dds", Dds),
412
"image/vnd.radiance" => feature_gate!("hdr", Hdr),
413
"image/gif" => feature_gate!("gif", Gif),
414
"image/x-icon" => feature_gate!("ico", Ico),
415
"image/jpeg" => feature_gate!("jpeg", Jpeg),
416
"image/ktx2" => feature_gate!("ktx2", Ktx2),
417
"image/png" => feature_gate!("png", Png),
418
"image/qoi" | "image/x-qoi" => feature_gate!("qoi", Qoi),
419
"image/x-exr" => feature_gate!("exr", OpenExr),
420
"image/x-portable-bitmap"
421
| "image/x-portable-graymap"
422
| "image/x-portable-pixmap"
423
| "image/x-portable-anymap" => feature_gate!("pnm", Pnm),
424
"image/x-targa" | "image/x-tga" => feature_gate!("tga", Tga),
425
"image/tiff" => feature_gate!("tiff", Tiff),
426
"image/webp" => feature_gate!("webp", WebP),
427
_ => return None,
428
})
429
}
430
431
pub fn from_extension(extension: &str) -> Option<Self> {
432
#[expect(
433
clippy::allow_attributes,
434
reason = "`unreachable_code` may not always lint"
435
)]
436
#[allow(
437
unreachable_code,
438
reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
439
)]
440
Some(match extension.to_ascii_lowercase().as_str() {
441
"basis" => feature_gate!("basis-universal", Basis),
442
"bmp" => feature_gate!("bmp", Bmp),
443
"dds" => feature_gate!("dds", Dds),
444
"ff" | "farbfeld" => feature_gate!("ff", Farbfeld),
445
"gif" => feature_gate!("gif", Gif),
446
"exr" => feature_gate!("exr", OpenExr),
447
"hdr" => feature_gate!("hdr", Hdr),
448
"ico" => feature_gate!("ico", Ico),
449
"jpg" | "jpeg" => feature_gate!("jpeg", Jpeg),
450
"ktx2" => feature_gate!("ktx2", Ktx2),
451
"pam" | "pbm" | "pgm" | "ppm" => feature_gate!("pnm", Pnm),
452
"png" => feature_gate!("png", Png),
453
"qoi" => feature_gate!("qoi", Qoi),
454
"tga" => feature_gate!("tga", Tga),
455
"tif" | "tiff" => feature_gate!("tiff", Tiff),
456
"webp" => feature_gate!("webp", WebP),
457
_ => return None,
458
})
459
}
460
461
pub fn as_image_crate_format(&self) -> Option<image::ImageFormat> {
462
#[expect(
463
clippy::allow_attributes,
464
reason = "`unreachable_code` may not always lint"
465
)]
466
#[allow(
467
unreachable_code,
468
reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
469
)]
470
Some(match self {
471
#[cfg(feature = "bmp")]
472
ImageFormat::Bmp => image::ImageFormat::Bmp,
473
#[cfg(feature = "dds")]
474
ImageFormat::Dds => image::ImageFormat::Dds,
475
#[cfg(feature = "ff")]
476
ImageFormat::Farbfeld => image::ImageFormat::Farbfeld,
477
#[cfg(feature = "gif")]
478
ImageFormat::Gif => image::ImageFormat::Gif,
479
#[cfg(feature = "exr")]
480
ImageFormat::OpenExr => image::ImageFormat::OpenExr,
481
#[cfg(feature = "hdr")]
482
ImageFormat::Hdr => image::ImageFormat::Hdr,
483
#[cfg(feature = "ico")]
484
ImageFormat::Ico => image::ImageFormat::Ico,
485
#[cfg(feature = "jpeg")]
486
ImageFormat::Jpeg => image::ImageFormat::Jpeg,
487
#[cfg(feature = "png")]
488
ImageFormat::Png => image::ImageFormat::Png,
489
#[cfg(feature = "pnm")]
490
ImageFormat::Pnm => image::ImageFormat::Pnm,
491
#[cfg(feature = "qoi")]
492
ImageFormat::Qoi => image::ImageFormat::Qoi,
493
#[cfg(feature = "tga")]
494
ImageFormat::Tga => image::ImageFormat::Tga,
495
#[cfg(feature = "tiff")]
496
ImageFormat::Tiff => image::ImageFormat::Tiff,
497
#[cfg(feature = "webp")]
498
ImageFormat::WebP => image::ImageFormat::WebP,
499
#[cfg(feature = "basis-universal")]
500
ImageFormat::Basis => return None,
501
#[cfg(feature = "ktx2")]
502
ImageFormat::Ktx2 => return None,
503
// FIXME: https://github.com/rust-lang/rust/issues/129031
504
#[expect(
505
clippy::allow_attributes,
506
reason = "`unreachable_patterns` may not always lint"
507
)]
508
#[allow(
509
unreachable_patterns,
510
reason = "The wildcard pattern will be unreachable if all formats are enabled; otherwise, it will be reachable"
511
)]
512
_ => return None,
513
})
514
}
515
516
pub fn from_image_crate_format(format: image::ImageFormat) -> Option<ImageFormat> {
517
#[expect(
518
clippy::allow_attributes,
519
reason = "`unreachable_code` may not always lint"
520
)]
521
#[allow(
522
unreachable_code,
523
reason = "If all features listed below are disabled, then all arms will have a `return None`, keeping the surrounding `Some()` from being constructed."
524
)]
525
Some(match format {
526
image::ImageFormat::Bmp => feature_gate!("bmp", Bmp),
527
image::ImageFormat::Dds => feature_gate!("dds", Dds),
528
image::ImageFormat::Farbfeld => feature_gate!("ff", Farbfeld),
529
image::ImageFormat::Gif => feature_gate!("gif", Gif),
530
image::ImageFormat::OpenExr => feature_gate!("exr", OpenExr),
531
image::ImageFormat::Hdr => feature_gate!("hdr", Hdr),
532
image::ImageFormat::Ico => feature_gate!("ico", Ico),
533
image::ImageFormat::Jpeg => feature_gate!("jpeg", Jpeg),
534
image::ImageFormat::Png => feature_gate!("png", Png),
535
image::ImageFormat::Pnm => feature_gate!("pnm", Pnm),
536
image::ImageFormat::Qoi => feature_gate!("qoi", Qoi),
537
image::ImageFormat::Tga => feature_gate!("tga", Tga),
538
image::ImageFormat::Tiff => feature_gate!("tiff", Tiff),
539
image::ImageFormat::WebP => feature_gate!("webp", WebP),
540
_ => return None,
541
})
542
}
543
}
544
545
pub trait ToExtents {
546
fn to_extents(self) -> Extent3d;
547
}
548
impl ToExtents for UVec2 {
549
fn to_extents(self) -> Extent3d {
550
Extent3d {
551
width: self.x,
552
height: self.y,
553
depth_or_array_layers: 1,
554
}
555
}
556
}
557
impl ToExtents for UVec3 {
558
fn to_extents(self) -> Extent3d {
559
Extent3d {
560
width: self.x,
561
height: self.y,
562
depth_or_array_layers: self.z,
563
}
564
}
565
}
566
567
/// An image, optimized for usage in rendering.
568
///
569
/// ## Remote Inspection
570
///
571
/// To transmit an [`Image`] between two running Bevy apps, e.g. through BRP, use [`SerializedImage`](crate::SerializedImage).
572
/// This type is only meant for short-term transmission between same versions and should not be stored anywhere.
573
#[derive(Asset, Debug, Clone, PartialEq)]
574
#[cfg_attr(
575
feature = "bevy_reflect",
576
derive(Reflect),
577
reflect(opaque, Default, Debug, Clone)
578
)]
579
#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))]
580
pub struct Image {
581
/// Raw pixel data.
582
/// If the image is being used as a storage texture which doesn't need to be initialized by the
583
/// CPU, then this should be `None`.
584
/// Otherwise, it should always be `Some`.
585
pub data: Option<Vec<u8>>,
586
/// For texture data with layers and mips, this field controls how wgpu interprets the buffer layout.
587
///
588
/// Use [`TextureDataOrder::default()`] for all other cases.
589
pub data_order: TextureDataOrder,
590
// TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors.
591
/// Describes the data layout of the GPU texture.\
592
/// For example, whether a texture contains 1D/2D/3D data, and what the format of the texture data is.
593
///
594
/// ## Field Usage Notes
595
/// - [`TextureDescriptor::label`] is used for caching purposes when not using `Asset<Image>`.\
596
/// If you use assets, the label is purely a debugging aid.
597
/// - [`TextureDescriptor::view_formats`] is currently unused by Bevy.
598
pub texture_descriptor: TextureDescriptor<Option<&'static str>, &'static [TextureFormat]>,
599
/// The [`ImageSampler`] to use during rendering.
600
pub sampler: ImageSampler,
601
/// Describes how the GPU texture should be interpreted.\
602
/// For example, 2D image data could be read as plain 2D, an array texture of layers of 2D with the same dimensions (and the number of layers in that case),
603
/// a cube map, an array of cube maps, etc.
604
///
605
/// ## Field Usage Notes
606
/// - [`TextureViewDescriptor::label`] is used for caching purposes when not using `Asset<Image>`.\
607
/// If you use assets, the label is purely a debugging aid.
608
pub texture_view_descriptor: Option<TextureViewDescriptor<Option<&'static str>>>,
609
pub asset_usage: RenderAssetUsages,
610
/// Whether this image should be copied on the GPU when resized.
611
pub copy_on_resize: bool,
612
}
613
614
/// Used in [`Image`], this determines what image sampler to use when rendering. The default setting,
615
/// [`ImageSampler::Default`], will read the sampler from the `ImagePlugin` at setup.
616
/// Setting this to [`ImageSampler::Descriptor`] will override the global default descriptor for this [`Image`].
617
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
618
#[cfg_attr(
619
feature = "bevy_reflect",
620
derive(Reflect),
621
reflect(Default, Debug, Clone)
622
)]
623
pub enum ImageSampler {
624
/// Default image sampler, derived from the `ImagePlugin` setup.
625
#[default]
626
Default,
627
/// Custom sampler for this image which will override global default.
628
Descriptor(ImageSamplerDescriptor),
629
}
630
631
impl ImageSampler {
632
/// Returns an image sampler with [`ImageFilterMode::Linear`] min and mag filters
633
#[inline]
634
pub fn linear() -> ImageSampler {
635
ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
636
}
637
638
/// Returns an image sampler with [`ImageFilterMode::Nearest`] min and mag filters
639
#[inline]
640
pub fn nearest() -> ImageSampler {
641
ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
642
}
643
644
/// Initialize the descriptor if it is not already initialized.
645
///
646
/// Descriptor is typically initialized by Bevy when the image is loaded,
647
/// so this is convenient shortcut for updating the descriptor.
648
pub fn get_or_init_descriptor(&mut self) -> &mut ImageSamplerDescriptor {
649
match self {
650
ImageSampler::Default => {
651
*self = ImageSampler::Descriptor(ImageSamplerDescriptor::default());
652
match self {
653
ImageSampler::Descriptor(descriptor) => descriptor,
654
_ => unreachable!(),
655
}
656
}
657
ImageSampler::Descriptor(descriptor) => descriptor,
658
}
659
}
660
}
661
662
/// How edges should be handled in texture addressing.
663
///
664
/// See [`ImageSamplerDescriptor`] for information how to configure this.
665
///
666
/// This type mirrors [`AddressMode`].
667
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
668
#[cfg_attr(
669
feature = "bevy_reflect",
670
derive(Reflect),
671
reflect(Default, Debug, Clone)
672
)]
673
pub enum ImageAddressMode {
674
/// Clamp the value to the edge of the texture.
675
///
676
/// -0.25 -> 0.0
677
/// 1.25 -> 1.0
678
#[default]
679
ClampToEdge,
680
/// Repeat the texture in a tiling fashion.
681
///
682
/// -0.25 -> 0.75
683
/// 1.25 -> 0.25
684
Repeat,
685
/// Repeat the texture, mirroring it every repeat.
686
///
687
/// -0.25 -> 0.25
688
/// 1.25 -> 0.75
689
MirrorRepeat,
690
/// Clamp the value to the border of the texture
691
/// Requires the wgpu feature [`Features::ADDRESS_MODE_CLAMP_TO_BORDER`].
692
///
693
/// -0.25 -> border
694
/// 1.25 -> border
695
ClampToBorder,
696
}
697
698
/// Texel mixing mode when sampling between texels.
699
///
700
/// This type mirrors [`FilterMode`].
701
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
702
#[cfg_attr(
703
feature = "bevy_reflect",
704
derive(Reflect),
705
reflect(Default, Debug, Clone)
706
)]
707
pub enum ImageFilterMode {
708
/// Nearest neighbor sampling.
709
///
710
/// This creates a pixelated effect when used as a mag filter.
711
#[default]
712
Nearest,
713
/// Linear Interpolation.
714
///
715
/// This makes textures smooth but blurry when used as a mag filter.
716
Linear,
717
}
718
719
/// Comparison function used for depth and stencil operations.
720
///
721
/// This type mirrors [`CompareFunction`].
722
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
723
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
724
pub enum ImageCompareFunction {
725
/// Function never passes
726
Never,
727
/// Function passes if new value less than existing value
728
Less,
729
/// Function passes if new value is equal to existing value. When using
730
/// this compare function, make sure to mark your Vertex Shader's `@builtin(position)`
731
/// output as `@invariant` to prevent artifacting.
732
Equal,
733
/// Function passes if new value is less than or equal to existing value
734
LessEqual,
735
/// Function passes if new value is greater than existing value
736
Greater,
737
/// Function passes if new value is not equal to existing value. When using
738
/// this compare function, make sure to mark your Vertex Shader's `@builtin(position)`
739
/// output as `@invariant` to prevent artifacting.
740
NotEqual,
741
/// Function passes if new value is greater than or equal to existing value
742
GreaterEqual,
743
/// Function always passes
744
Always,
745
}
746
747
/// Color variation to use when the sampler addressing mode is [`ImageAddressMode::ClampToBorder`].
748
///
749
/// This type mirrors [`SamplerBorderColor`].
750
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
751
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
752
pub enum ImageSamplerBorderColor {
753
/// RGBA color `[0, 0, 0, 0]`.
754
TransparentBlack,
755
/// RGBA color `[0, 0, 0, 1]`.
756
OpaqueBlack,
757
/// RGBA color `[1, 1, 1, 1]`.
758
OpaqueWhite,
759
/// On the Metal wgpu backend, this is equivalent to [`Self::TransparentBlack`] for
760
/// textures that have an alpha component, and equivalent to [`Self::OpaqueBlack`]
761
/// for textures that do not have an alpha component. On other backends,
762
/// this is equivalent to [`Self::TransparentBlack`]. Requires
763
/// [`Features::ADDRESS_MODE_CLAMP_TO_ZERO`]. Not supported on the web.
764
Zero,
765
}
766
767
/// Indicates to an `ImageLoader` how an [`Image`] should be sampled.
768
///
769
/// As this type is part of the `ImageLoaderSettings`,
770
/// it will be serialized to an image asset `.meta` file which might require a migration in case of
771
/// a breaking change.
772
///
773
/// This types mirrors [`SamplerDescriptor`], but that might change in future versions.
774
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
775
#[cfg_attr(
776
feature = "bevy_reflect",
777
derive(Reflect),
778
reflect(Default, Debug, Clone)
779
)]
780
pub struct ImageSamplerDescriptor {
781
pub label: Option<String>,
782
/// How to deal with out of bounds accesses in the u (i.e. x) direction.
783
pub address_mode_u: ImageAddressMode,
784
/// How to deal with out of bounds accesses in the v (i.e. y) direction.
785
pub address_mode_v: ImageAddressMode,
786
/// How to deal with out of bounds accesses in the w (i.e. z) direction.
787
pub address_mode_w: ImageAddressMode,
788
/// How to filter the texture when it needs to be magnified (made larger).
789
pub mag_filter: ImageFilterMode,
790
/// How to filter the texture when it needs to be minified (made smaller).
791
pub min_filter: ImageFilterMode,
792
/// How to filter between mip map levels
793
pub mipmap_filter: ImageFilterMode,
794
/// Minimum level of detail (i.e. mip level) to use.
795
pub lod_min_clamp: f32,
796
/// Maximum level of detail (i.e. mip level) to use.
797
pub lod_max_clamp: f32,
798
/// If this is enabled, this is a comparison sampler using the given comparison function.
799
pub compare: Option<ImageCompareFunction>,
800
/// Must be at least 1. If this is not 1, all filter modes must be linear.
801
pub anisotropy_clamp: u16,
802
/// Border color to use when `address_mode` is [`ImageAddressMode::ClampToBorder`].
803
pub border_color: Option<ImageSamplerBorderColor>,
804
}
805
806
impl Default for ImageSamplerDescriptor {
807
fn default() -> Self {
808
Self {
809
address_mode_u: Default::default(),
810
address_mode_v: Default::default(),
811
address_mode_w: Default::default(),
812
mag_filter: Default::default(),
813
min_filter: Default::default(),
814
mipmap_filter: Default::default(),
815
lod_min_clamp: 0.0,
816
lod_max_clamp: 32.0,
817
compare: None,
818
anisotropy_clamp: 1,
819
border_color: None,
820
label: None,
821
}
822
}
823
}
824
825
impl ImageSamplerDescriptor {
826
/// Returns a sampler descriptor with [`Linear`](ImageFilterMode::Linear) min and mag filters
827
#[inline]
828
pub fn linear() -> ImageSamplerDescriptor {
829
ImageSamplerDescriptor {
830
mag_filter: ImageFilterMode::Linear,
831
min_filter: ImageFilterMode::Linear,
832
mipmap_filter: ImageFilterMode::Linear,
833
..Default::default()
834
}
835
}
836
837
/// Returns a sampler descriptor with [`Nearest`](ImageFilterMode::Nearest) min and mag filters
838
#[inline]
839
pub fn nearest() -> ImageSamplerDescriptor {
840
ImageSamplerDescriptor {
841
mag_filter: ImageFilterMode::Nearest,
842
min_filter: ImageFilterMode::Nearest,
843
mipmap_filter: ImageFilterMode::Nearest,
844
..Default::default()
845
}
846
}
847
848
/// Returns this sampler descriptor with a new `ImageFilterMode` min, mag, and mipmap filters
849
#[inline]
850
pub fn set_filter(&mut self, filter: ImageFilterMode) -> &mut Self {
851
self.mag_filter = filter;
852
self.min_filter = filter;
853
self.mipmap_filter = filter;
854
self
855
}
856
857
/// Returns this sampler descriptor with a new `ImageAddressMode` for u, v, and w
858
#[inline]
859
pub fn set_address_mode(&mut self, address_mode: ImageAddressMode) -> &mut Self {
860
self.address_mode_u = address_mode;
861
self.address_mode_v = address_mode;
862
self.address_mode_w = address_mode;
863
self
864
}
865
866
/// Returns this sampler descriptor with an `anisotropy_clamp` value and also
867
/// set filters to `ImageFilterMode::Linear`, which is required to
868
/// use anisotropy.
869
#[inline]
870
pub fn set_anisotropic_filter(&mut self, anisotropy_clamp: u16) -> &mut Self {
871
self.mag_filter = ImageFilterMode::Linear;
872
self.min_filter = ImageFilterMode::Linear;
873
self.mipmap_filter = ImageFilterMode::Linear;
874
self.anisotropy_clamp = anisotropy_clamp;
875
self
876
}
877
878
pub fn as_wgpu(&self) -> SamplerDescriptor<Option<&str>> {
879
SamplerDescriptor {
880
label: self.label.as_deref(),
881
address_mode_u: self.address_mode_u.into(),
882
address_mode_v: self.address_mode_v.into(),
883
address_mode_w: self.address_mode_w.into(),
884
mag_filter: self.mag_filter.into(),
885
min_filter: self.min_filter.into(),
886
mipmap_filter: self.mipmap_filter.into(),
887
lod_min_clamp: self.lod_min_clamp,
888
lod_max_clamp: self.lod_max_clamp,
889
compare: self.compare.map(Into::into),
890
anisotropy_clamp: self.anisotropy_clamp,
891
border_color: self.border_color.map(Into::into),
892
}
893
}
894
}
895
896
impl From<ImageAddressMode> for AddressMode {
897
fn from(value: ImageAddressMode) -> Self {
898
match value {
899
ImageAddressMode::ClampToEdge => AddressMode::ClampToEdge,
900
ImageAddressMode::Repeat => AddressMode::Repeat,
901
ImageAddressMode::MirrorRepeat => AddressMode::MirrorRepeat,
902
ImageAddressMode::ClampToBorder => AddressMode::ClampToBorder,
903
}
904
}
905
}
906
907
impl From<ImageFilterMode> for FilterMode {
908
fn from(value: ImageFilterMode) -> Self {
909
match value {
910
ImageFilterMode::Nearest => FilterMode::Nearest,
911
ImageFilterMode::Linear => FilterMode::Linear,
912
}
913
}
914
}
915
916
impl From<ImageFilterMode> for MipmapFilterMode {
917
fn from(value: ImageFilterMode) -> Self {
918
match value {
919
ImageFilterMode::Nearest => MipmapFilterMode::Nearest,
920
ImageFilterMode::Linear => MipmapFilterMode::Linear,
921
}
922
}
923
}
924
925
impl From<ImageCompareFunction> for CompareFunction {
926
fn from(value: ImageCompareFunction) -> Self {
927
match value {
928
ImageCompareFunction::Never => CompareFunction::Never,
929
ImageCompareFunction::Less => CompareFunction::Less,
930
ImageCompareFunction::Equal => CompareFunction::Equal,
931
ImageCompareFunction::LessEqual => CompareFunction::LessEqual,
932
ImageCompareFunction::Greater => CompareFunction::Greater,
933
ImageCompareFunction::NotEqual => CompareFunction::NotEqual,
934
ImageCompareFunction::GreaterEqual => CompareFunction::GreaterEqual,
935
ImageCompareFunction::Always => CompareFunction::Always,
936
}
937
}
938
}
939
940
impl From<ImageSamplerBorderColor> for SamplerBorderColor {
941
fn from(value: ImageSamplerBorderColor) -> Self {
942
match value {
943
ImageSamplerBorderColor::TransparentBlack => SamplerBorderColor::TransparentBlack,
944
ImageSamplerBorderColor::OpaqueBlack => SamplerBorderColor::OpaqueBlack,
945
ImageSamplerBorderColor::OpaqueWhite => SamplerBorderColor::OpaqueWhite,
946
ImageSamplerBorderColor::Zero => SamplerBorderColor::Zero,
947
}
948
}
949
}
950
951
impl From<AddressMode> for ImageAddressMode {
952
fn from(value: AddressMode) -> Self {
953
match value {
954
AddressMode::ClampToEdge => ImageAddressMode::ClampToEdge,
955
AddressMode::Repeat => ImageAddressMode::Repeat,
956
AddressMode::MirrorRepeat => ImageAddressMode::MirrorRepeat,
957
AddressMode::ClampToBorder => ImageAddressMode::ClampToBorder,
958
}
959
}
960
}
961
962
impl From<MipmapFilterMode> for ImageFilterMode {
963
fn from(value: MipmapFilterMode) -> Self {
964
match value {
965
MipmapFilterMode::Nearest => ImageFilterMode::Nearest,
966
MipmapFilterMode::Linear => ImageFilterMode::Linear,
967
}
968
}
969
}
970
971
impl From<FilterMode> for ImageFilterMode {
972
fn from(value: FilterMode) -> Self {
973
match value {
974
FilterMode::Nearest => ImageFilterMode::Nearest,
975
FilterMode::Linear => ImageFilterMode::Linear,
976
}
977
}
978
}
979
980
impl From<CompareFunction> for ImageCompareFunction {
981
fn from(value: CompareFunction) -> Self {
982
match value {
983
CompareFunction::Never => ImageCompareFunction::Never,
984
CompareFunction::Less => ImageCompareFunction::Less,
985
CompareFunction::Equal => ImageCompareFunction::Equal,
986
CompareFunction::LessEqual => ImageCompareFunction::LessEqual,
987
CompareFunction::Greater => ImageCompareFunction::Greater,
988
CompareFunction::NotEqual => ImageCompareFunction::NotEqual,
989
CompareFunction::GreaterEqual => ImageCompareFunction::GreaterEqual,
990
CompareFunction::Always => ImageCompareFunction::Always,
991
}
992
}
993
}
994
995
impl From<SamplerBorderColor> for ImageSamplerBorderColor {
996
fn from(value: SamplerBorderColor) -> Self {
997
match value {
998
SamplerBorderColor::TransparentBlack => ImageSamplerBorderColor::TransparentBlack,
999
SamplerBorderColor::OpaqueBlack => ImageSamplerBorderColor::OpaqueBlack,
1000
SamplerBorderColor::OpaqueWhite => ImageSamplerBorderColor::OpaqueWhite,
1001
SamplerBorderColor::Zero => ImageSamplerBorderColor::Zero,
1002
}
1003
}
1004
}
1005
1006
impl From<SamplerDescriptor<Option<&str>>> for ImageSamplerDescriptor {
1007
fn from(value: SamplerDescriptor<Option<&str>>) -> Self {
1008
ImageSamplerDescriptor {
1009
label: value.label.map(ToString::to_string),
1010
address_mode_u: value.address_mode_u.into(),
1011
address_mode_v: value.address_mode_v.into(),
1012
address_mode_w: value.address_mode_w.into(),
1013
mag_filter: value.mag_filter.into(),
1014
min_filter: value.min_filter.into(),
1015
mipmap_filter: value.mipmap_filter.into(),
1016
lod_min_clamp: value.lod_min_clamp,
1017
lod_max_clamp: value.lod_max_clamp,
1018
compare: value.compare.map(Into::into),
1019
anisotropy_clamp: value.anisotropy_clamp,
1020
border_color: value.border_color.map(Into::into),
1021
}
1022
}
1023
}
1024
1025
impl Default for Image {
1026
/// default is a 1x1x1 all '1.0' texture
1027
fn default() -> Self {
1028
let mut image = Image::default_uninit();
1029
image.data = Some(vec![
1030
255;
1031
image
1032
.texture_descriptor
1033
.format
1034
.pixel_size()
1035
.unwrap_or(0)
1036
]);
1037
image
1038
}
1039
}
1040
1041
impl Image {
1042
/// Creates a new image from raw binary data and the corresponding metadata.
1043
///
1044
/// # Panics
1045
/// Panics if the length of the `data`, volume of the `size` and the size of the `format`
1046
/// do not match.
1047
pub fn new(
1048
size: Extent3d,
1049
dimension: TextureDimension,
1050
data: Vec<u8>,
1051
format: TextureFormat,
1052
asset_usage: RenderAssetUsages,
1053
) -> Self {
1054
if let Ok(pixel_size) = format.pixel_size() {
1055
debug_assert_eq!(
1056
size.volume() * pixel_size,
1057
data.len(),
1058
"Pixel data, size and format have to match",
1059
);
1060
}
1061
let mut image = Image::new_uninit(size, dimension, format, asset_usage);
1062
image.data = Some(data);
1063
image
1064
}
1065
1066
/// Exactly the same as [`Image::new`], but doesn't initialize the image
1067
pub fn new_uninit(
1068
size: Extent3d,
1069
dimension: TextureDimension,
1070
format: TextureFormat,
1071
asset_usage: RenderAssetUsages,
1072
) -> Self {
1073
Image {
1074
data: None,
1075
data_order: TextureDataOrder::default(),
1076
texture_descriptor: TextureDescriptor {
1077
size,
1078
format,
1079
dimension,
1080
label: None,
1081
mip_level_count: 1,
1082
sample_count: 1,
1083
usage: TextureUsages::TEXTURE_BINDING
1084
| TextureUsages::COPY_DST
1085
| TextureUsages::COPY_SRC,
1086
view_formats: &[],
1087
},
1088
sampler: ImageSampler::Default,
1089
texture_view_descriptor: None,
1090
asset_usage,
1091
copy_on_resize: false,
1092
}
1093
}
1094
1095
/// A transparent white 1x1x1 image.
1096
///
1097
/// Contrast to [`Image::default`], which is opaque.
1098
pub fn transparent() -> Image {
1099
// We rely on the default texture format being RGBA8UnormSrgb
1100
// when constructing a transparent color from bytes.
1101
// If this changes, this function will need to be updated.
1102
let format = TextureFormat::bevy_default();
1103
if let Ok(pixel_size) = format.pixel_size() {
1104
debug_assert!(pixel_size == 4);
1105
}
1106
let data = vec![255, 255, 255, 0];
1107
Image::new(
1108
Extent3d::default(),
1109
TextureDimension::D2,
1110
data,
1111
format,
1112
RenderAssetUsages::default(),
1113
)
1114
}
1115
/// Creates a new uninitialized 1x1x1 image
1116
pub fn default_uninit() -> Image {
1117
Image::new_uninit(
1118
Extent3d::default(),
1119
TextureDimension::D2,
1120
TextureFormat::bevy_default(),
1121
RenderAssetUsages::default(),
1122
)
1123
}
1124
1125
/// Creates a new image from raw binary data and the corresponding metadata, by filling
1126
/// the image data with the `pixel` data repeated multiple times.
1127
///
1128
/// # Panics
1129
/// Panics if the size of the `format` is not a multiple of the length of the `pixel` data.
1130
pub fn new_fill(
1131
size: Extent3d,
1132
dimension: TextureDimension,
1133
pixel: &[u8],
1134
format: TextureFormat,
1135
asset_usage: RenderAssetUsages,
1136
) -> Self {
1137
let mut image = Image::new_uninit(size, dimension, format, asset_usage);
1138
if let Ok(pixel_size) = image.texture_descriptor.format.pixel_size()
1139
&& pixel_size > 0
1140
{
1141
let byte_len = pixel_size * size.volume();
1142
debug_assert_eq!(
1143
pixel.len() % pixel_size,
1144
0,
1145
"Must not have incomplete pixel data (pixel size is {}B).",
1146
pixel_size,
1147
);
1148
debug_assert!(
1149
pixel.len() <= byte_len,
1150
"Fill data must fit within pixel buffer (expected {byte_len}B).",
1151
);
1152
let data = pixel.iter().copied().cycle().take(byte_len).collect();
1153
image.data = Some(data);
1154
}
1155
image
1156
}
1157
1158
/// Create a new zero-filled image with a given size, which can be rendered to.
1159
/// Useful for mirrors, UI, or exporting images for example.
1160
/// This is primarily for use as a render target for a [`Camera`].
1161
/// See [`RenderTarget::Image`].
1162
///
1163
/// For Standard Dynamic Range (SDR) images you can use [`TextureFormat::Rgba8UnormSrgb`].
1164
/// For High Dynamic Range (HDR) images you can use [`TextureFormat::Rgba16Float`].
1165
///
1166
/// The default [`TextureUsages`] are
1167
/// [`TEXTURE_BINDING`](TextureUsages::TEXTURE_BINDING),
1168
/// [`COPY_DST`](TextureUsages::COPY_DST),
1169
/// [`RENDER_ATTACHMENT`](TextureUsages::RENDER_ATTACHMENT).
1170
///
1171
/// The default [`RenderAssetUsages`] is [`MAIN_WORLD | RENDER_WORLD`](RenderAssetUsages::default)
1172
/// so that it is accessible from the CPU and GPU.
1173
/// You can customize this by changing the [`asset_usage`](Image::asset_usage) field.
1174
///
1175
/// [`Camera`]: https://docs.rs/bevy/latest/bevy/render/camera/struct.Camera.html
1176
/// [`RenderTarget::Image`]: https://docs.rs/bevy/latest/bevy/render/camera/enum.RenderTarget.html#variant.Image
1177
pub fn new_target_texture(
1178
width: u32,
1179
height: u32,
1180
format: TextureFormat,
1181
view_format: Option<TextureFormat>,
1182
) -> Self {
1183
let size = Extent3d {
1184
width,
1185
height,
1186
..Default::default()
1187
};
1188
// You need to set these texture usage flags in order to use the image as a render target
1189
let usage = TextureUsages::TEXTURE_BINDING
1190
| TextureUsages::COPY_DST
1191
| TextureUsages::RENDER_ATTACHMENT;
1192
// Fill with zeroes
1193
let data = vec![
1194
0;
1195
format.pixel_size().expect(
1196
"Failed to create Image: can't get pixel size for this TextureFormat"
1197
) * size.volume()
1198
];
1199
1200
Image {
1201
data: Some(data),
1202
data_order: TextureDataOrder::default(),
1203
texture_descriptor: TextureDescriptor {
1204
size,
1205
format,
1206
dimension: TextureDimension::D2,
1207
label: None,
1208
mip_level_count: 1,
1209
sample_count: 1,
1210
usage,
1211
view_formats: match view_format {
1212
Some(_) => format.srgb_view_formats(),
1213
None => &[],
1214
},
1215
},
1216
sampler: ImageSampler::Default,
1217
texture_view_descriptor: view_format.map(|f| TextureViewDescriptor {
1218
format: Some(f),
1219
..Default::default()
1220
}),
1221
asset_usage: RenderAssetUsages::default(),
1222
copy_on_resize: true,
1223
}
1224
}
1225
1226
/// Returns the width of a 2D image.
1227
#[inline]
1228
pub fn width(&self) -> u32 {
1229
self.texture_descriptor.size.width
1230
}
1231
1232
/// Returns the height of a 2D image.
1233
#[inline]
1234
pub fn height(&self) -> u32 {
1235
self.texture_descriptor.size.height
1236
}
1237
1238
/// Returns the aspect ratio (width / height) of a 2D image.
1239
#[inline]
1240
pub fn aspect_ratio(&self) -> AspectRatio {
1241
AspectRatio::try_from_pixels(self.width(), self.height()).expect(
1242
"Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
1243
)
1244
}
1245
1246
/// Returns the size of a 2D image as f32.
1247
#[inline]
1248
pub fn size_f32(&self) -> Vec2 {
1249
Vec2::new(self.width() as f32, self.height() as f32)
1250
}
1251
1252
/// Returns the size of a 2D image.
1253
#[inline]
1254
pub fn size(&self) -> UVec2 {
1255
UVec2::new(self.width(), self.height())
1256
}
1257
1258
/// Resizes the image to the new size, by removing information or appending 0 to the `data`.
1259
/// Does not properly scale the contents of the image.
1260
///
1261
/// If you need to keep pixel data intact, use [`Image::resize_in_place`].
1262
pub fn resize(&mut self, size: Extent3d) {
1263
self.texture_descriptor.size = size;
1264
if let Some(ref mut data) = self.data
1265
&& let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1266
{
1267
data.resize(size.volume() * pixel_size, 0);
1268
}
1269
}
1270
1271
/// Changes the `size` if the total number of data elements (pixels) remains the same.
1272
/// If not, returns [`TextureReinterpretationError::IncompatibleSizes`].
1273
pub fn reinterpret_size(
1274
&mut self,
1275
new_size: Extent3d,
1276
) -> Result<(), TextureReinterpretationError> {
1277
if new_size.volume() != self.texture_descriptor.size.volume() {
1278
return Err(TextureReinterpretationError::IncompatibleSizes {
1279
old: self.texture_descriptor.size,
1280
new: new_size,
1281
});
1282
}
1283
1284
self.texture_descriptor.size = new_size;
1285
Ok(())
1286
}
1287
1288
/// Resizes the image to the new size, keeping the pixel data intact, anchored at the top-left.
1289
/// When growing, the new space is filled with 0. When shrinking, the image is clipped.
1290
///
1291
/// For faster resizing when keeping pixel data intact is not important, use [`Image::resize`].
1292
pub fn resize_in_place(&mut self, new_size: Extent3d) {
1293
if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size() {
1294
let old_size = self.texture_descriptor.size;
1295
let byte_len = pixel_size * new_size.volume();
1296
self.texture_descriptor.size = new_size;
1297
1298
let Some(ref mut data) = self.data else {
1299
self.copy_on_resize = true;
1300
return;
1301
};
1302
1303
let mut new: Vec<u8> = vec![0; byte_len];
1304
1305
let copy_width = old_size.width.min(new_size.width) as usize;
1306
let copy_height = old_size.height.min(new_size.height) as usize;
1307
let copy_depth = old_size
1308
.depth_or_array_layers
1309
.min(new_size.depth_or_array_layers) as usize;
1310
1311
let old_row_stride = old_size.width as usize * pixel_size;
1312
let old_layer_stride = old_size.height as usize * old_row_stride;
1313
1314
let new_row_stride = new_size.width as usize * pixel_size;
1315
let new_layer_stride = new_size.height as usize * new_row_stride;
1316
1317
for z in 0..copy_depth {
1318
for y in 0..copy_height {
1319
let old_offset = z * old_layer_stride + y * old_row_stride;
1320
let new_offset = z * new_layer_stride + y * new_row_stride;
1321
1322
let old_range = (old_offset)..(old_offset + copy_width * pixel_size);
1323
let new_range = (new_offset)..(new_offset + copy_width * pixel_size);
1324
1325
new[new_range].copy_from_slice(&data[old_range]);
1326
}
1327
}
1328
1329
self.data = Some(new);
1330
}
1331
}
1332
1333
/// Takes a 2D image containing vertically stacked images of the same size, and reinterprets
1334
/// it as a 2D array texture, where each of the stacked images becomes one layer of the
1335
/// array. This is primarily for use with the `texture2DArray` shader uniform type.
1336
///
1337
/// # Errors
1338
/// Returns [`TextureReinterpretationError`] if the texture is not 2D, has more than one layers
1339
/// or is not evenly dividable into the `layers`.
1340
pub fn reinterpret_stacked_2d_as_array(
1341
&mut self,
1342
layers: u32,
1343
) -> Result<(), TextureReinterpretationError> {
1344
// Must be a stacked image, and the height must be divisible by layers.
1345
if self.texture_descriptor.dimension != TextureDimension::D2 {
1346
return Err(TextureReinterpretationError::WrongDimension);
1347
}
1348
if self.texture_descriptor.size.depth_or_array_layers != 1 {
1349
return Err(TextureReinterpretationError::InvalidLayerCount);
1350
}
1351
if !self.height().is_multiple_of(layers) {
1352
return Err(TextureReinterpretationError::HeightNotDivisibleByLayers {
1353
height: self.height(),
1354
layers,
1355
});
1356
}
1357
1358
self.reinterpret_size(Extent3d {
1359
width: self.width(),
1360
height: self.height() / layers,
1361
depth_or_array_layers: layers,
1362
})?;
1363
1364
Ok(())
1365
}
1366
1367
/// Convert a texture from a format to another. Only a few formats are
1368
/// supported as input and output:
1369
/// - `TextureFormat::R8Unorm`
1370
/// - `TextureFormat::Rg8Unorm`
1371
/// - `TextureFormat::Rgba8UnormSrgb`
1372
///
1373
/// To get [`Image`] as a [`image::DynamicImage`] see:
1374
/// [`Image::try_into_dynamic`].
1375
pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
1376
self.clone()
1377
.try_into_dynamic()
1378
.ok()
1379
.and_then(|img| match new_format {
1380
TextureFormat::R8Unorm => {
1381
Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
1382
}
1383
TextureFormat::Rg8Unorm => Some((
1384
image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
1385
false,
1386
)),
1387
TextureFormat::Rgba8UnormSrgb => {
1388
Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
1389
}
1390
_ => None,
1391
})
1392
.map(|(dyn_img, is_srgb)| Self::from_dynamic(dyn_img, is_srgb, self.asset_usage))
1393
}
1394
1395
/// Load a bytes buffer in a [`Image`], according to type `image_type`, using the `image`
1396
/// crate
1397
pub fn from_buffer(
1398
buffer: &[u8],
1399
image_type: ImageType,
1400
#[cfg_attr(
1401
not(any(feature = "basis-universal", feature = "dds", feature = "ktx2")),
1402
expect(unused_variables, reason = "only used with certain features")
1403
)]
1404
supported_compressed_formats: CompressedImageFormats,
1405
is_srgb: bool,
1406
image_sampler: ImageSampler,
1407
asset_usage: RenderAssetUsages,
1408
) -> Result<Image, TextureError> {
1409
let format = image_type.to_image_format()?;
1410
1411
// Load the image in the expected format.
1412
// Some formats like PNG allow for R or RG textures too, so the texture
1413
// format needs to be determined. For RGB textures an alpha channel
1414
// needs to be added, so the image data needs to be converted in those
1415
// cases.
1416
1417
let mut image = match format {
1418
#[cfg(feature = "basis-universal")]
1419
ImageFormat::Basis => {
1420
basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
1421
}
1422
#[cfg(feature = "dds")]
1423
ImageFormat::Dds => dds_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?,
1424
#[cfg(feature = "ktx2")]
1425
ImageFormat::Ktx2 => {
1426
ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
1427
}
1428
#[expect(
1429
clippy::allow_attributes,
1430
reason = "`unreachable_patterns` may not always lint"
1431
)]
1432
#[allow(
1433
unreachable_patterns,
1434
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"
1435
)]
1436
_ => {
1437
let image_crate_format = format
1438
.as_image_crate_format()
1439
.ok_or_else(|| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?;
1440
let mut reader = image::ImageReader::new(std::io::Cursor::new(buffer));
1441
reader.set_format(image_crate_format);
1442
reader.no_limits();
1443
let dyn_img = reader.decode()?;
1444
Self::from_dynamic(dyn_img, is_srgb, asset_usage)
1445
}
1446
};
1447
image.sampler = image_sampler;
1448
Ok(image)
1449
}
1450
1451
/// Whether the texture format is compressed or uncompressed
1452
pub fn is_compressed(&self) -> bool {
1453
let format_description = self.texture_descriptor.format;
1454
format_description
1455
.required_features()
1456
.contains(Features::TEXTURE_COMPRESSION_ASTC)
1457
|| format_description
1458
.required_features()
1459
.contains(Features::TEXTURE_COMPRESSION_BC)
1460
|| format_description
1461
.required_features()
1462
.contains(Features::TEXTURE_COMPRESSION_ETC2)
1463
}
1464
1465
/// Compute the byte offset where the data of a specific pixel is stored
1466
///
1467
/// Returns None if the provided coordinates are out of bounds.
1468
///
1469
/// For 2D textures, Z is the layer number. For 1D textures, Y and Z are ignored.
1470
#[inline(always)]
1471
pub fn pixel_data_offset(&self, coords: UVec3) -> Option<usize> {
1472
let width = self.texture_descriptor.size.width;
1473
let height = self.texture_descriptor.size.height;
1474
let depth = self.texture_descriptor.size.depth_or_array_layers;
1475
1476
let pixel_size = self.texture_descriptor.format.pixel_size().ok()?;
1477
let pixel_offset = match self.texture_descriptor.dimension {
1478
TextureDimension::D3 | TextureDimension::D2 => {
1479
if coords.x >= width || coords.y >= height || coords.z >= depth {
1480
return None;
1481
}
1482
coords.z * height * width + coords.y * width + coords.x
1483
}
1484
TextureDimension::D1 => {
1485
if coords.x >= width {
1486
return None;
1487
}
1488
coords.x
1489
}
1490
};
1491
1492
Some(pixel_offset as usize * pixel_size)
1493
}
1494
1495
/// Get a reference to the data bytes where a specific pixel's value is stored
1496
#[inline(always)]
1497
pub fn pixel_bytes(&self, coords: UVec3) -> Option<&[u8]> {
1498
let len = self.texture_descriptor.format.pixel_size().ok()?;
1499
let data = self.data.as_ref()?;
1500
self.pixel_data_offset(coords)
1501
.map(|start| &data[start..(start + len)])
1502
}
1503
1504
/// Get a mutable reference to the data bytes where a specific pixel's value is stored
1505
#[inline(always)]
1506
pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Option<&mut [u8]> {
1507
let len = self.texture_descriptor.format.pixel_size().ok()?;
1508
let offset = self.pixel_data_offset(coords);
1509
let data = self.data.as_mut()?;
1510
offset.map(|start| &mut data[start..(start + len)])
1511
}
1512
1513
/// Clears the content of the image with the given pixel. The image needs to be initialized on
1514
/// the cpu otherwise this is a noop.
1515
///
1516
/// This does nothing if the image data is not already initialized
1517
pub fn clear(&mut self, pixel: &[u8]) {
1518
if let Ok(pixel_size) = self.texture_descriptor.format.pixel_size()
1519
&& pixel_size > 0
1520
{
1521
let byte_len = pixel_size * self.texture_descriptor.size.volume();
1522
debug_assert_eq!(
1523
pixel.len() % pixel_size,
1524
0,
1525
"Must not have incomplete pixel data (pixel size is {}B).",
1526
pixel_size,
1527
);
1528
debug_assert!(
1529
pixel.len() <= byte_len,
1530
"Clear data must fit within pixel buffer (expected {byte_len}B).",
1531
);
1532
if let Some(data) = self.data.as_mut() {
1533
for pixel_data in data.chunks_mut(pixel_size) {
1534
pixel_data.copy_from_slice(pixel);
1535
}
1536
}
1537
}
1538
}
1539
1540
/// Read the color of a specific pixel (1D texture).
1541
///
1542
/// See [`get_color_at`](Self::get_color_at) for more details.
1543
#[inline(always)]
1544
pub fn get_color_at_1d(&self, x: u32) -> Result<Color, TextureAccessError> {
1545
if self.texture_descriptor.dimension != TextureDimension::D1 {
1546
return Err(TextureAccessError::WrongDimension);
1547
}
1548
self.get_color_at_internal(UVec3::new(x, 0, 0))
1549
}
1550
1551
/// Read the color of a specific pixel (2D texture).
1552
///
1553
/// This function will find the raw byte data of a specific pixel and
1554
/// decode it into a user-friendly [`Color`] struct for you.
1555
///
1556
/// Supports many of the common [`TextureFormat`]s:
1557
/// - RGBA/BGRA 8-bit unsigned integer, both sRGB and Linear
1558
/// - 16-bit and 32-bit unsigned integer
1559
/// - 16-bit and 32-bit float
1560
///
1561
/// Be careful: as the data is converted to [`Color`] (which uses `f32` internally),
1562
/// there may be issues with precision when using non-f32 [`TextureFormat`]s.
1563
/// If you read a value you previously wrote using `set_color_at`, it will not match.
1564
/// If you are working with a 32-bit integer [`TextureFormat`], the value will be
1565
/// inaccurate (as `f32` does not have enough bits to represent it exactly).
1566
///
1567
/// Single channel (R) formats are assumed to represent grayscale, so the value
1568
/// will be copied to all three RGB channels in the resulting [`Color`].
1569
///
1570
/// Other [`TextureFormat`]s are unsupported, such as:
1571
/// - block-compressed formats
1572
/// - non-byte-aligned formats like 10-bit
1573
/// - signed integer formats
1574
#[inline(always)]
1575
pub fn get_color_at(&self, x: u32, y: u32) -> Result<Color, TextureAccessError> {
1576
if self.texture_descriptor.dimension != TextureDimension::D2 {
1577
return Err(TextureAccessError::WrongDimension);
1578
}
1579
self.get_color_at_internal(UVec3::new(x, y, 0))
1580
}
1581
1582
/// Read the color of a specific pixel (2D texture with layers or 3D texture).
1583
///
1584
/// See [`get_color_at`](Self::get_color_at) for more details.
1585
#[inline(always)]
1586
pub fn get_color_at_3d(&self, x: u32, y: u32, z: u32) -> Result<Color, TextureAccessError> {
1587
match (
1588
self.texture_descriptor.dimension,
1589
self.texture_descriptor.size.depth_or_array_layers,
1590
) {
1591
(TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1592
self.get_color_at_internal(UVec3::new(x, y, z))
1593
}
1594
_ => Err(TextureAccessError::WrongDimension),
1595
}
1596
}
1597
1598
/// Change the color of a specific pixel (1D texture).
1599
///
1600
/// See [`set_color_at`](Self::set_color_at) for more details.
1601
#[inline(always)]
1602
pub fn set_color_at_1d(&mut self, x: u32, color: Color) -> Result<(), TextureAccessError> {
1603
if self.texture_descriptor.dimension != TextureDimension::D1 {
1604
return Err(TextureAccessError::WrongDimension);
1605
}
1606
self.set_color_at_internal(UVec3::new(x, 0, 0), color)
1607
}
1608
1609
/// Change the color of a specific pixel (2D texture).
1610
///
1611
/// This function will find the raw byte data of a specific pixel and
1612
/// change it according to a [`Color`] you provide. The [`Color`] struct
1613
/// will be encoded into the [`Image`]'s [`TextureFormat`].
1614
///
1615
/// Supports many of the common [`TextureFormat`]s:
1616
/// - RGBA/BGRA 8-bit unsigned integer, both sRGB and Linear
1617
/// - 16-bit and 32-bit unsigned integer (with possibly-limited precision, as [`Color`] uses `f32`)
1618
/// - 16-bit and 32-bit float
1619
///
1620
/// Be careful: writing to non-f32 [`TextureFormat`]s is lossy! The data has to be converted,
1621
/// so if you read it back using `get_color_at`, the `Color` you get will not equal the value
1622
/// you used when writing it using this function.
1623
///
1624
/// For R and RG formats, only the respective values from the linear RGB [`Color`] will be used.
1625
///
1626
/// Other [`TextureFormat`]s are unsupported, such as:
1627
/// - block-compressed formats
1628
/// - non-byte-aligned formats like 10-bit
1629
/// - signed integer formats
1630
#[inline(always)]
1631
pub fn set_color_at(&mut self, x: u32, y: u32, color: Color) -> Result<(), TextureAccessError> {
1632
if self.texture_descriptor.dimension != TextureDimension::D2 {
1633
return Err(TextureAccessError::WrongDimension);
1634
}
1635
self.set_color_at_internal(UVec3::new(x, y, 0), color)
1636
}
1637
1638
/// Change the color of a specific pixel (2D texture with layers or 3D texture).
1639
///
1640
/// See [`set_color_at`](Self::set_color_at) for more details.
1641
#[inline(always)]
1642
pub fn set_color_at_3d(
1643
&mut self,
1644
x: u32,
1645
y: u32,
1646
z: u32,
1647
color: Color,
1648
) -> Result<(), TextureAccessError> {
1649
match (
1650
self.texture_descriptor.dimension,
1651
self.texture_descriptor.size.depth_or_array_layers,
1652
) {
1653
(TextureDimension::D3, _) | (TextureDimension::D2, 2..) => {
1654
self.set_color_at_internal(UVec3::new(x, y, z), color)
1655
}
1656
_ => Err(TextureAccessError::WrongDimension),
1657
}
1658
}
1659
1660
#[inline(always)]
1661
fn get_color_at_internal(&self, coords: UVec3) -> Result<Color, TextureAccessError> {
1662
let Some(bytes) = self.pixel_bytes(coords) else {
1663
return Err(TextureAccessError::OutOfBounds {
1664
x: coords.x,
1665
y: coords.y,
1666
z: coords.z,
1667
});
1668
};
1669
1670
// NOTE: GPUs are always Little Endian.
1671
// Make sure to respect that when we create color values from bytes.
1672
match self.texture_descriptor.format {
1673
TextureFormat::Rgba8UnormSrgb => Ok(Color::srgba(
1674
bytes[0] as f32 / u8::MAX as f32,
1675
bytes[1] as f32 / u8::MAX as f32,
1676
bytes[2] as f32 / u8::MAX as f32,
1677
bytes[3] as f32 / u8::MAX as f32,
1678
)),
1679
TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => Ok(Color::linear_rgba(
1680
bytes[0] as f32 / u8::MAX as f32,
1681
bytes[1] as f32 / u8::MAX as f32,
1682
bytes[2] as f32 / u8::MAX as f32,
1683
bytes[3] as f32 / u8::MAX as f32,
1684
)),
1685
TextureFormat::Bgra8UnormSrgb => Ok(Color::srgba(
1686
bytes[2] as f32 / u8::MAX as f32,
1687
bytes[1] as f32 / u8::MAX as f32,
1688
bytes[0] as f32 / u8::MAX as f32,
1689
bytes[3] as f32 / u8::MAX as f32,
1690
)),
1691
TextureFormat::Bgra8Unorm => Ok(Color::linear_rgba(
1692
bytes[2] as f32 / u8::MAX as f32,
1693
bytes[1] as f32 / u8::MAX as f32,
1694
bytes[0] as f32 / u8::MAX as f32,
1695
bytes[3] as f32 / u8::MAX as f32,
1696
)),
1697
TextureFormat::Rgba32Float => Ok(Color::linear_rgba(
1698
f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1699
f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1700
f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1701
f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1702
)),
1703
TextureFormat::Rgba16Float => Ok(Color::linear_rgba(
1704
half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32(),
1705
half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32(),
1706
half::f16::from_le_bytes([bytes[4], bytes[5]]).to_f32(),
1707
half::f16::from_le_bytes([bytes[6], bytes[7]]).to_f32(),
1708
)),
1709
TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1710
let (r, g, b, a) = (
1711
u16::from_le_bytes([bytes[0], bytes[1]]),
1712
u16::from_le_bytes([bytes[2], bytes[3]]),
1713
u16::from_le_bytes([bytes[4], bytes[5]]),
1714
u16::from_le_bytes([bytes[6], bytes[7]]),
1715
);
1716
Ok(Color::linear_rgba(
1717
// going via f64 to avoid rounding errors with large numbers and division
1718
(r as f64 / u16::MAX as f64) as f32,
1719
(g as f64 / u16::MAX as f64) as f32,
1720
(b as f64 / u16::MAX as f64) as f32,
1721
(a as f64 / u16::MAX as f64) as f32,
1722
))
1723
}
1724
TextureFormat::Rgba32Uint => {
1725
let (r, g, b, a) = (
1726
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1727
u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1728
u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1729
u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1730
);
1731
Ok(Color::linear_rgba(
1732
// going via f64 to avoid rounding errors with large numbers and division
1733
(r as f64 / u32::MAX as f64) as f32,
1734
(g as f64 / u32::MAX as f64) as f32,
1735
(b as f64 / u32::MAX as f64) as f32,
1736
(a as f64 / u32::MAX as f64) as f32,
1737
))
1738
}
1739
// assume R-only texture format means grayscale (linear)
1740
// copy value to all of RGB in Color
1741
TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1742
let x = bytes[0] as f32 / u8::MAX as f32;
1743
Ok(Color::linear_rgb(x, x, x))
1744
}
1745
TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1746
let x = u16::from_le_bytes([bytes[0], bytes[1]]);
1747
// going via f64 to avoid rounding errors with large numbers and division
1748
let x = (x as f64 / u16::MAX as f64) as f32;
1749
Ok(Color::linear_rgb(x, x, x))
1750
}
1751
TextureFormat::R32Uint => {
1752
let x = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1753
// going via f64 to avoid rounding errors with large numbers and division
1754
let x = (x as f64 / u32::MAX as f64) as f32;
1755
Ok(Color::linear_rgb(x, x, x))
1756
}
1757
TextureFormat::R16Float => {
1758
let x = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1759
Ok(Color::linear_rgb(x, x, x))
1760
}
1761
TextureFormat::R32Float => {
1762
let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1763
Ok(Color::linear_rgb(x, x, x))
1764
}
1765
TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1766
let r = bytes[0] as f32 / u8::MAX as f32;
1767
let g = bytes[1] as f32 / u8::MAX as f32;
1768
Ok(Color::linear_rgb(r, g, 0.0))
1769
}
1770
TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1771
let r = u16::from_le_bytes([bytes[0], bytes[1]]);
1772
let g = u16::from_le_bytes([bytes[2], bytes[3]]);
1773
// going via f64 to avoid rounding errors with large numbers and division
1774
let r = (r as f64 / u16::MAX as f64) as f32;
1775
let g = (g as f64 / u16::MAX as f64) as f32;
1776
Ok(Color::linear_rgb(r, g, 0.0))
1777
}
1778
TextureFormat::Rg32Uint => {
1779
let r = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1780
let g = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1781
// going via f64 to avoid rounding errors with large numbers and division
1782
let r = (r as f64 / u32::MAX as f64) as f32;
1783
let g = (g as f64 / u32::MAX as f64) as f32;
1784
Ok(Color::linear_rgb(r, g, 0.0))
1785
}
1786
TextureFormat::Rg16Float => {
1787
let r = half::f16::from_le_bytes([bytes[0], bytes[1]]).to_f32();
1788
let g = half::f16::from_le_bytes([bytes[2], bytes[3]]).to_f32();
1789
Ok(Color::linear_rgb(r, g, 0.0))
1790
}
1791
TextureFormat::Rg32Float => {
1792
let r = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1793
let g = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1794
Ok(Color::linear_rgb(r, g, 0.0))
1795
}
1796
_ => Err(TextureAccessError::UnsupportedTextureFormat(
1797
self.texture_descriptor.format,
1798
)),
1799
}
1800
}
1801
1802
#[inline(always)]
1803
fn set_color_at_internal(
1804
&mut self,
1805
coords: UVec3,
1806
color: Color,
1807
) -> Result<(), TextureAccessError> {
1808
let format = self.texture_descriptor.format;
1809
1810
let Some(bytes) = self.pixel_bytes_mut(coords) else {
1811
return Err(TextureAccessError::OutOfBounds {
1812
x: coords.x,
1813
y: coords.y,
1814
z: coords.z,
1815
});
1816
};
1817
1818
// NOTE: GPUs are always Little Endian.
1819
// Make sure to respect that when we convert color values to bytes.
1820
match format {
1821
TextureFormat::Rgba8UnormSrgb => {
1822
let [r, g, b, a] = Srgba::from(color).to_f32_array();
1823
bytes[0] = (r * u8::MAX as f32) as u8;
1824
bytes[1] = (g * u8::MAX as f32) as u8;
1825
bytes[2] = (b * u8::MAX as f32) as u8;
1826
bytes[3] = (a * u8::MAX as f32) as u8;
1827
}
1828
TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => {
1829
let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1830
bytes[0] = (r * u8::MAX as f32) as u8;
1831
bytes[1] = (g * u8::MAX as f32) as u8;
1832
bytes[2] = (b * u8::MAX as f32) as u8;
1833
bytes[3] = (a * u8::MAX as f32) as u8;
1834
}
1835
TextureFormat::Bgra8UnormSrgb => {
1836
let [r, g, b, a] = Srgba::from(color).to_f32_array();
1837
bytes[0] = (b * u8::MAX as f32) as u8;
1838
bytes[1] = (g * u8::MAX as f32) as u8;
1839
bytes[2] = (r * u8::MAX as f32) as u8;
1840
bytes[3] = (a * u8::MAX as f32) as u8;
1841
}
1842
TextureFormat::Bgra8Unorm => {
1843
let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1844
bytes[0] = (b * u8::MAX as f32) as u8;
1845
bytes[1] = (g * u8::MAX as f32) as u8;
1846
bytes[2] = (r * u8::MAX as f32) as u8;
1847
bytes[3] = (a * u8::MAX as f32) as u8;
1848
}
1849
TextureFormat::Rgba16Float => {
1850
let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1851
bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1852
bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1853
bytes[4..6].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(b)));
1854
bytes[6..8].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(a)));
1855
}
1856
TextureFormat::Rgba32Float => {
1857
let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1858
bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1859
bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1860
bytes[8..12].copy_from_slice(&f32::to_le_bytes(b));
1861
bytes[12..16].copy_from_slice(&f32::to_le_bytes(a));
1862
}
1863
TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1864
let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1865
let [r, g, b, a] = [
1866
(r * u16::MAX as f32) as u16,
1867
(g * u16::MAX as f32) as u16,
1868
(b * u16::MAX as f32) as u16,
1869
(a * u16::MAX as f32) as u16,
1870
];
1871
bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1872
bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1873
bytes[4..6].copy_from_slice(&u16::to_le_bytes(b));
1874
bytes[6..8].copy_from_slice(&u16::to_le_bytes(a));
1875
}
1876
TextureFormat::Rgba32Uint => {
1877
let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1878
let [r, g, b, a] = [
1879
(r * u32::MAX as f32) as u32,
1880
(g * u32::MAX as f32) as u32,
1881
(b * u32::MAX as f32) as u32,
1882
(a * u32::MAX as f32) as u32,
1883
];
1884
bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1885
bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1886
bytes[8..12].copy_from_slice(&u32::to_le_bytes(b));
1887
bytes[12..16].copy_from_slice(&u32::to_le_bytes(a));
1888
}
1889
TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1890
// Convert to grayscale with minimal loss if color is already gray
1891
let linear = LinearRgba::from(color);
1892
let luminance = Xyza::from(linear).y;
1893
let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1894
bytes[0] = (r * u8::MAX as f32) as u8;
1895
}
1896
TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1897
// Convert to grayscale with minimal loss if color is already gray
1898
let linear = LinearRgba::from(color);
1899
let luminance = Xyza::from(linear).y;
1900
let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1901
let r = (r * u16::MAX as f32) as u16;
1902
bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1903
}
1904
TextureFormat::R32Uint => {
1905
// Convert to grayscale with minimal loss if color is already gray
1906
let linear = LinearRgba::from(color);
1907
let luminance = Xyza::from(linear).y;
1908
let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1909
// go via f64 to avoid imprecision
1910
let r = (r as f64 * u32::MAX as f64) as u32;
1911
bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1912
}
1913
TextureFormat::R16Float => {
1914
// Convert to grayscale with minimal loss if color is already gray
1915
let linear = LinearRgba::from(color);
1916
let luminance = Xyza::from(linear).y;
1917
let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1918
let x = half::f16::from_f32(r);
1919
bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(x));
1920
}
1921
TextureFormat::R32Float => {
1922
// Convert to grayscale with minimal loss if color is already gray
1923
let linear = LinearRgba::from(color);
1924
let luminance = Xyza::from(linear).y;
1925
let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1926
bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1927
}
1928
TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1929
let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1930
bytes[0] = (r * u8::MAX as f32) as u8;
1931
bytes[1] = (g * u8::MAX as f32) as u8;
1932
}
1933
TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1934
let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1935
let r = (r * u16::MAX as f32) as u16;
1936
let g = (g * u16::MAX as f32) as u16;
1937
bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1938
bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1939
}
1940
TextureFormat::Rg32Uint => {
1941
let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1942
// go via f64 to avoid imprecision
1943
let r = (r as f64 * u32::MAX as f64) as u32;
1944
let g = (g as f64 * u32::MAX as f64) as u32;
1945
bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1946
bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1947
}
1948
TextureFormat::Rg16Float => {
1949
let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1950
bytes[0..2].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(r)));
1951
bytes[2..4].copy_from_slice(&half::f16::to_le_bytes(half::f16::from_f32(g)));
1952
}
1953
TextureFormat::Rg32Float => {
1954
let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1955
bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1956
bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1957
}
1958
_ => {
1959
return Err(TextureAccessError::UnsupportedTextureFormat(
1960
self.texture_descriptor.format,
1961
));
1962
}
1963
}
1964
Ok(())
1965
}
1966
}
1967
1968
#[derive(Clone, Copy, Debug)]
1969
pub enum DataFormat {
1970
Rgb,
1971
Rgba,
1972
Rrr,
1973
Rrrg,
1974
Rg,
1975
}
1976
1977
/// Texture data need to be transcoded from this format for use with `wgpu`.
1978
#[derive(Clone, Copy, Debug)]
1979
pub enum TranscodeFormat {
1980
Etc1s,
1981
Uastc(DataFormat),
1982
/// Has to be transcoded from `R8UnormSrgb` to `R8Unorm` for use with `wgpu`.
1983
R8UnormSrgb,
1984
/// Has to be transcoded from `Rg8UnormSrgb` to `R8G8Unorm` for use with `wgpu`.
1985
Rg8UnormSrgb,
1986
/// Has to be transcoded from `Rgb8` to `Rgba8` for use with `wgpu`.
1987
Rgb8,
1988
}
1989
1990
/// An error that occurs when reinterpreting an image.
1991
#[derive(Error, Debug)]
1992
pub enum TextureReinterpretationError {
1993
#[error("incompatible sizes: old = {old:?} new = {new:?}")]
1994
IncompatibleSizes { old: Extent3d, new: Extent3d },
1995
#[error("must be a 2d image")]
1996
WrongDimension,
1997
#[error("must not already be a layered image")]
1998
InvalidLayerCount,
1999
#[error("can not evenly divide height = {height} by layers = {layers}")]
2000
HeightNotDivisibleByLayers { height: u32, layers: u32 },
2001
}
2002
2003
/// An error that occurs when accessing specific pixels in a texture.
2004
#[derive(Error, Debug)]
2005
pub enum TextureAccessError {
2006
#[error("out of bounds (x: {x}, y: {y}, z: {z})")]
2007
OutOfBounds { x: u32, y: u32, z: u32 },
2008
#[error("unsupported texture format: {0:?}")]
2009
UnsupportedTextureFormat(TextureFormat),
2010
#[error("attempt to access texture with different dimension")]
2011
WrongDimension,
2012
}
2013
2014
/// An error that occurs when loading a texture.
2015
#[derive(Error, Debug)]
2016
pub enum TextureError {
2017
/// Image MIME type is invalid.
2018
#[error("invalid image mime type: {0}")]
2019
InvalidImageMimeType(String),
2020
/// Image extension is invalid.
2021
#[error("invalid image extension: {0}")]
2022
InvalidImageExtension(String),
2023
/// Failed to load an image.
2024
#[error("failed to load an image: {0}")]
2025
ImageError(#[from] image::ImageError),
2026
/// Texture format isn't supported.
2027
#[error("unsupported texture format: {0}")]
2028
UnsupportedTextureFormat(String),
2029
/// Supercompression isn't supported.
2030
#[error("supercompression not supported: {0}")]
2031
SuperCompressionNotSupported(String),
2032
/// Failed to decompress an image.
2033
#[error("failed to decompress an image: {0}")]
2034
SuperDecompressionError(String),
2035
/// Invalid data.
2036
#[error("invalid data: {0}")]
2037
InvalidData(String),
2038
/// Transcode error.
2039
#[error("transcode error: {0}")]
2040
TranscodeError(String),
2041
/// Format requires transcoding.
2042
#[error("format requires transcoding: {0:?}")]
2043
FormatRequiresTranscodingError(TranscodeFormat),
2044
/// Only cubemaps with six faces are supported.
2045
#[error("only cubemaps with six faces are supported")]
2046
IncompleteCubemap,
2047
}
2048
2049
/// The type of a raw image buffer.
2050
#[derive(Debug)]
2051
pub enum ImageType<'a> {
2052
/// The mime type of an image, for example `"image/png"`.
2053
MimeType(&'a str),
2054
/// The extension of an image file, for example `"png"`.
2055
Extension(&'a str),
2056
/// The direct format of the image
2057
Format(ImageFormat),
2058
}
2059
2060
impl<'a> ImageType<'a> {
2061
pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
2062
match self {
2063
ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
2064
.ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
2065
ImageType::Extension(extension) => ImageFormat::from_extension(extension)
2066
.ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
2067
ImageType::Format(format) => Ok(*format),
2068
}
2069
}
2070
}
2071
2072
/// Used to calculate the volume of an item.
2073
pub trait Volume {
2074
fn volume(&self) -> usize;
2075
}
2076
2077
impl Volume for Extent3d {
2078
/// Calculates the volume of the [`Extent3d`].
2079
fn volume(&self) -> usize {
2080
(self.width * self.height * self.depth_or_array_layers) as usize
2081
}
2082
}
2083
2084
/// Extends the wgpu [`TextureFormat`] with information about the pixel.
2085
pub trait TextureFormatPixelInfo {
2086
/// Returns the size of a pixel in bytes of the format.
2087
/// error with `TextureAccessError::UnsupportedTextureFormat` if the format is compressed.
2088
fn pixel_size(&self) -> Result<usize, TextureAccessError>;
2089
}
2090
2091
impl TextureFormatPixelInfo for TextureFormat {
2092
fn pixel_size(&self) -> Result<usize, TextureAccessError> {
2093
let info = self;
2094
match info.block_dimensions() {
2095
(1, 1) => Ok(info.block_copy_size(None).unwrap() as usize),
2096
_ => Err(TextureAccessError::UnsupportedTextureFormat(*self)),
2097
}
2098
}
2099
}
2100
2101
bitflags::bitflags! {
2102
#[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
2103
#[repr(transparent)]
2104
pub struct CompressedImageFormats: u32 {
2105
const NONE = 0;
2106
const ASTC_LDR = 1 << 0;
2107
const BC = 1 << 1;
2108
const ETC2 = 1 << 2;
2109
}
2110
}
2111
2112
impl CompressedImageFormats {
2113
pub fn from_features(features: Features) -> Self {
2114
let mut supported_compressed_formats = Self::default();
2115
if features.contains(Features::TEXTURE_COMPRESSION_ASTC) {
2116
supported_compressed_formats |= Self::ASTC_LDR;
2117
}
2118
if features.contains(Features::TEXTURE_COMPRESSION_BC) {
2119
supported_compressed_formats |= Self::BC;
2120
}
2121
if features.contains(Features::TEXTURE_COMPRESSION_ETC2) {
2122
supported_compressed_formats |= Self::ETC2;
2123
}
2124
supported_compressed_formats
2125
}
2126
2127
pub fn supports(&self, format: TextureFormat) -> bool {
2128
match format {
2129
TextureFormat::Bc1RgbaUnorm
2130
| TextureFormat::Bc1RgbaUnormSrgb
2131
| TextureFormat::Bc2RgbaUnorm
2132
| TextureFormat::Bc2RgbaUnormSrgb
2133
| TextureFormat::Bc3RgbaUnorm
2134
| TextureFormat::Bc3RgbaUnormSrgb
2135
| TextureFormat::Bc4RUnorm
2136
| TextureFormat::Bc4RSnorm
2137
| TextureFormat::Bc5RgUnorm
2138
| TextureFormat::Bc5RgSnorm
2139
| TextureFormat::Bc6hRgbUfloat
2140
| TextureFormat::Bc6hRgbFloat
2141
| TextureFormat::Bc7RgbaUnorm
2142
| TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
2143
TextureFormat::Etc2Rgb8Unorm
2144
| TextureFormat::Etc2Rgb8UnormSrgb
2145
| TextureFormat::Etc2Rgb8A1Unorm
2146
| TextureFormat::Etc2Rgb8A1UnormSrgb
2147
| TextureFormat::Etc2Rgba8Unorm
2148
| TextureFormat::Etc2Rgba8UnormSrgb
2149
| TextureFormat::EacR11Unorm
2150
| TextureFormat::EacR11Snorm
2151
| TextureFormat::EacRg11Unorm
2152
| TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
2153
TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
2154
_ => true,
2155
}
2156
}
2157
}
2158
2159
/// For defining which compressed image formats are supported. This will be initialized from available device features
2160
/// in `finish()` of the bevy `RenderPlugin`, but is left for the user to specify if not using the `RenderPlugin`, or
2161
/// the WGPU backend.
2162
#[derive(Resource)]
2163
pub struct CompressedImageFormatSupport(pub CompressedImageFormats);
2164
2165
#[cfg(test)]
2166
mod test {
2167
use super::*;
2168
2169
#[test]
2170
fn image_size() {
2171
let size = Extent3d {
2172
width: 200,
2173
height: 100,
2174
depth_or_array_layers: 1,
2175
};
2176
let image = Image::new_fill(
2177
size,
2178
TextureDimension::D2,
2179
&[0, 0, 0, 255],
2180
TextureFormat::Rgba8Unorm,
2181
RenderAssetUsages::MAIN_WORLD,
2182
);
2183
assert_eq!(
2184
Vec2::new(size.width as f32, size.height as f32),
2185
image.size_f32()
2186
);
2187
}
2188
2189
#[test]
2190
fn image_default_size() {
2191
let image = Image::default();
2192
assert_eq!(UVec2::ONE, image.size());
2193
assert_eq!(Vec2::ONE, image.size_f32());
2194
}
2195
2196
#[test]
2197
fn on_edge_pixel_is_invalid() {
2198
let image = Image::new_fill(
2199
Extent3d {
2200
width: 5,
2201
height: 10,
2202
depth_or_array_layers: 1,
2203
},
2204
TextureDimension::D2,
2205
&[0, 0, 0, 255],
2206
TextureFormat::Rgba8Unorm,
2207
RenderAssetUsages::MAIN_WORLD,
2208
);
2209
assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK)));
2210
assert!(matches!(
2211
image.get_color_at(0, 10),
2212
Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 })
2213
));
2214
assert!(matches!(
2215
image.get_color_at(5, 10),
2216
Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 })
2217
));
2218
}
2219
2220
#[test]
2221
fn get_set_pixel_2d_with_layers() {
2222
let mut image = Image::new_fill(
2223
Extent3d {
2224
width: 5,
2225
height: 10,
2226
depth_or_array_layers: 3,
2227
},
2228
TextureDimension::D2,
2229
&[0, 0, 0, 255],
2230
TextureFormat::Rgba8Unorm,
2231
RenderAssetUsages::MAIN_WORLD,
2232
);
2233
image.set_color_at_3d(0, 0, 0, Color::WHITE).unwrap();
2234
assert!(matches!(image.get_color_at_3d(0, 0, 0), Ok(Color::WHITE)));
2235
image.set_color_at_3d(2, 3, 1, Color::WHITE).unwrap();
2236
assert!(matches!(image.get_color_at_3d(2, 3, 1), Ok(Color::WHITE)));
2237
image.set_color_at_3d(4, 9, 2, Color::WHITE).unwrap();
2238
assert!(matches!(image.get_color_at_3d(4, 9, 2), Ok(Color::WHITE)));
2239
}
2240
2241
#[test]
2242
fn resize_in_place_2d_grow_and_shrink() {
2243
use bevy_color::ColorToPacked;
2244
2245
const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2246
const GROW_FILL: LinearRgba = LinearRgba::NONE;
2247
2248
let mut image = Image::new_fill(
2249
Extent3d {
2250
width: 2,
2251
height: 2,
2252
depth_or_array_layers: 1,
2253
},
2254
TextureDimension::D2,
2255
&INITIAL_FILL.to_u8_array(),
2256
TextureFormat::Rgba8Unorm,
2257
RenderAssetUsages::MAIN_WORLD,
2258
);
2259
2260
// Create a test pattern
2261
2262
const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2263
(0, 1, LinearRgba::RED),
2264
(1, 1, LinearRgba::GREEN),
2265
(1, 0, LinearRgba::BLUE),
2266
];
2267
2268
for (x, y, color) in &TEST_PIXELS {
2269
image.set_color_at(*x, *y, Color::from(*color)).unwrap();
2270
}
2271
2272
// Grow image
2273
image.resize_in_place(Extent3d {
2274
width: 4,
2275
height: 4,
2276
depth_or_array_layers: 1,
2277
});
2278
2279
// After growing, the test pattern should be the same.
2280
assert!(matches!(
2281
image.get_color_at(0, 0),
2282
Ok(Color::LinearRgba(INITIAL_FILL))
2283
));
2284
for (x, y, color) in &TEST_PIXELS {
2285
assert_eq!(
2286
image.get_color_at(*x, *y).unwrap(),
2287
Color::LinearRgba(*color)
2288
);
2289
}
2290
2291
// Pixels in the newly added area should get filled with zeroes.
2292
assert!(matches!(
2293
image.get_color_at(3, 3),
2294
Ok(Color::LinearRgba(GROW_FILL))
2295
));
2296
2297
// Shrink
2298
image.resize_in_place(Extent3d {
2299
width: 1,
2300
height: 1,
2301
depth_or_array_layers: 1,
2302
});
2303
2304
// Images outside of the new dimensions should be clipped
2305
assert!(image.get_color_at(1, 1).is_err());
2306
}
2307
2308
#[test]
2309
fn resize_in_place_array_grow_and_shrink() {
2310
use bevy_color::ColorToPacked;
2311
2312
const INITIAL_FILL: LinearRgba = LinearRgba::BLACK;
2313
const GROW_FILL: LinearRgba = LinearRgba::NONE;
2314
const LAYERS: u32 = 4;
2315
2316
let mut image = Image::new_fill(
2317
Extent3d {
2318
width: 2,
2319
height: 2,
2320
depth_or_array_layers: LAYERS,
2321
},
2322
TextureDimension::D2,
2323
&INITIAL_FILL.to_u8_array(),
2324
TextureFormat::Rgba8Unorm,
2325
RenderAssetUsages::MAIN_WORLD,
2326
);
2327
2328
// Create a test pattern
2329
2330
const TEST_PIXELS: [(u32, u32, LinearRgba); 3] = [
2331
(0, 1, LinearRgba::RED),
2332
(1, 1, LinearRgba::GREEN),
2333
(1, 0, LinearRgba::BLUE),
2334
];
2335
2336
for z in 0..LAYERS {
2337
for (x, y, color) in &TEST_PIXELS {
2338
image
2339
.set_color_at_3d(*x, *y, z, Color::from(*color))
2340
.unwrap();
2341
}
2342
}
2343
2344
// Grow image
2345
image.resize_in_place(Extent3d {
2346
width: 4,
2347
height: 4,
2348
depth_or_array_layers: LAYERS + 1,
2349
});
2350
2351
// After growing, the test pattern should be the same.
2352
assert!(matches!(
2353
image.get_color_at(0, 0),
2354
Ok(Color::LinearRgba(INITIAL_FILL))
2355
));
2356
for z in 0..LAYERS {
2357
for (x, y, color) in &TEST_PIXELS {
2358
assert_eq!(
2359
image.get_color_at_3d(*x, *y, z).unwrap(),
2360
Color::LinearRgba(*color)
2361
);
2362
}
2363
}
2364
2365
// Pixels in the newly added area should get filled with zeroes.
2366
for z in 0..(LAYERS + 1) {
2367
assert!(matches!(
2368
image.get_color_at_3d(3, 3, z),
2369
Ok(Color::LinearRgba(GROW_FILL))
2370
));
2371
}
2372
2373
// Shrink
2374
image.resize_in_place(Extent3d {
2375
width: 1,
2376
height: 1,
2377
depth_or_array_layers: 1,
2378
});
2379
2380
// Images outside of the new dimensions should be clipped
2381
assert!(image.get_color_at_3d(1, 1, 0).is_err());
2382
2383
// Higher layers should no longer be present
2384
assert!(image.get_color_at_3d(0, 0, 1).is_err());
2385
2386
// Grow layers
2387
image.resize_in_place(Extent3d {
2388
width: 1,
2389
height: 1,
2390
depth_or_array_layers: 2,
2391
});
2392
2393
// Pixels in the newly added layer should be zeroes.
2394
assert!(matches!(
2395
image.get_color_at_3d(0, 0, 1),
2396
Ok(Color::LinearRgba(GROW_FILL))
2397
));
2398
}
2399
2400
#[test]
2401
fn image_clear() {
2402
let mut image = Image::new_fill(
2403
Extent3d {
2404
width: 32,
2405
height: 32,
2406
depth_or_array_layers: 1,
2407
},
2408
TextureDimension::D2,
2409
&[0; 4],
2410
TextureFormat::Rgba8Snorm,
2411
RenderAssetUsages::all(),
2412
);
2413
2414
assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 0));
2415
2416
image.clear(&[255; 4]);
2417
2418
assert!(image.data.as_ref().unwrap().iter().all(|&p| p == 255));
2419
}
2420
2421
#[test]
2422
fn get_or_init_sampler_modifications() {
2423
// given some sampler
2424
let mut default_sampler = ImageSampler::Default;
2425
// a load_with_settings call wants to customize the descriptor
2426
let my_sampler_in_a_loader = default_sampler
2427
.get_or_init_descriptor()
2428
.set_filter(ImageFilterMode::Linear)
2429
.set_address_mode(ImageAddressMode::Repeat);
2430
2431
assert_eq!(
2432
my_sampler_in_a_loader.address_mode_u,
2433
ImageAddressMode::Repeat
2434
);
2435
assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2436
}
2437
2438
#[test]
2439
fn get_or_init_sampler_anisotropy() {
2440
// given some sampler
2441
let mut default_sampler = ImageSampler::Default;
2442
// a load_with_settings call wants to customize the descriptor
2443
let my_sampler_in_a_loader = default_sampler
2444
.get_or_init_descriptor()
2445
.set_anisotropic_filter(8);
2446
2447
assert_eq!(my_sampler_in_a_loader.min_filter, ImageFilterMode::Linear);
2448
assert_eq!(my_sampler_in_a_loader.anisotropy_clamp, 8);
2449
}
2450
}
2451
2452