mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 20:53:53 +00:00
## Solution Exposes the image <-> "texture" as methods on `Image`. ## Extra I'm wondering if `image_texture_conversion.rs` should be renamed to `image_conversion.rs`. That or the file be deleted altogether in favour of putting the code alongside the rest of the `Image` impl. Its kind-of weird to refer to the `Image` as a texture. Also `Image::convert` is a public method so I didn't want to edit its signature, but it might be nice to have the function consume the image instead of just passing a reference to it because it would eliminate a clone. ## Changelog > Rename `image_to_texture` to `Image::from_dynamic` > Rename `texture_to_image` to `Image::try_into_dynamic` > `Image::try_into_dynamic` now returns a `Result` (this is to make it easier for users who didn't read that only a few conversions are supported to figure it out.)
This commit is contained in:
parent
6a54683491
commit
cbb884cb02
2 changed files with 226 additions and 185 deletions
|
@ -5,7 +5,6 @@ use super::dds::*;
|
|||
#[cfg(feature = "ktx2")]
|
||||
use super::ktx2::*;
|
||||
|
||||
use super::image_texture_conversion::image_to_texture;
|
||||
use crate::{
|
||||
render_asset::{PrepareAssetError, RenderAsset},
|
||||
render_resource::{Sampler, Texture, TextureView},
|
||||
|
@ -342,13 +341,18 @@ impl Image {
|
|||
});
|
||||
}
|
||||
|
||||
/// Convert a texture from a format to another
|
||||
/// Only a few formats are supported as input and output:
|
||||
/// Convert a texture from a format to another. Only a few formats are
|
||||
/// supported as input and output:
|
||||
/// - `TextureFormat::R8Unorm`
|
||||
/// - `TextureFormat::Rg8Unorm`
|
||||
/// - `TextureFormat::Rgba8UnormSrgb`
|
||||
///
|
||||
/// To get [`Image`] as a [`image::DynamicImage`] see:
|
||||
/// [`Image::try_into_dynamic`].
|
||||
pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
|
||||
super::image_texture_conversion::texture_to_image(self)
|
||||
self.clone()
|
||||
.try_into_dynamic()
|
||||
.ok()
|
||||
.and_then(|img| match new_format {
|
||||
TextureFormat::R8Unorm => {
|
||||
Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
|
||||
|
@ -362,9 +366,7 @@ impl Image {
|
|||
}
|
||||
_ => None,
|
||||
})
|
||||
.map(|(dyn_img, is_srgb)| {
|
||||
super::image_texture_conversion::image_to_texture(dyn_img, is_srgb)
|
||||
})
|
||||
.map(|(dyn_img, is_srgb)| Self::from_dynamic(dyn_img, is_srgb))
|
||||
}
|
||||
|
||||
/// Load a bytes buffer in a [`Image`], according to type `image_type`, using the `image`
|
||||
|
@ -402,7 +404,7 @@ impl Image {
|
|||
reader.set_format(image_crate_format);
|
||||
reader.no_limits();
|
||||
let dyn_img = reader.decode()?;
|
||||
Ok(image_to_texture(dyn_img, is_srgb))
|
||||
Ok(Self::from_dynamic(dyn_img, is_srgb))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,194 +1,233 @@
|
|||
use crate::texture::{Image, TextureFormatPixelInfo};
|
||||
use anyhow::anyhow;
|
||||
use image::{DynamicImage, ImageBuffer};
|
||||
use wgpu::{Extent3d, TextureDimension, TextureFormat};
|
||||
|
||||
// TODO: fix name?
|
||||
/// Converts a [`DynamicImage`] to an [`Image`].
|
||||
pub(crate) fn image_to_texture(dyn_img: DynamicImage, is_srgb: bool) -> Image {
|
||||
use bevy_core::cast_slice;
|
||||
let width;
|
||||
let height;
|
||||
impl Image {
|
||||
/// Converts a [`DynamicImage`] to an [`Image`].
|
||||
pub fn from_dynamic(dyn_img: DynamicImage, is_srgb: bool) -> Image {
|
||||
use bevy_core::cast_slice;
|
||||
let width;
|
||||
let height;
|
||||
|
||||
let data: Vec<u8>;
|
||||
let format: TextureFormat;
|
||||
let data: Vec<u8>;
|
||||
let format: TextureFormat;
|
||||
|
||||
match dyn_img {
|
||||
DynamicImage::ImageLuma8(i) => {
|
||||
let i = DynamicImage::ImageLuma8(i).into_rgba8();
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
match dyn_img {
|
||||
DynamicImage::ImageLuma8(i) => {
|
||||
let i = DynamicImage::ImageLuma8(i).into_rgba8();
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
DynamicImage::ImageLumaA8(i) => {
|
||||
let i = DynamicImage::ImageLumaA8(i).into_rgba8();
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
DynamicImage::ImageRgb8(i) => {
|
||||
let i = DynamicImage::ImageRgb8(i).into_rgba8();
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
DynamicImage::ImageRgba8(i) => {
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
DynamicImage::ImageLuma16(i) => {
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::R16Uint;
|
||||
|
||||
let raw_data = i.into_raw();
|
||||
|
||||
data = cast_slice(&raw_data).to_owned();
|
||||
}
|
||||
DynamicImage::ImageLumaA16(i) => {
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::Rg16Uint;
|
||||
|
||||
let raw_data = i.into_raw();
|
||||
|
||||
data = cast_slice(&raw_data).to_owned();
|
||||
}
|
||||
DynamicImage::ImageRgb16(image) => {
|
||||
width = image.width();
|
||||
height = image.height();
|
||||
format = TextureFormat::Rgba16Uint;
|
||||
|
||||
let mut local_data =
|
||||
Vec::with_capacity(width as usize * height as usize * format.pixel_size());
|
||||
|
||||
for pixel in image.into_raw().chunks_exact(3) {
|
||||
// TODO: use the array_chunks method once stabilised
|
||||
// https://github.com/rust-lang/rust/issues/74985
|
||||
let r = pixel[0];
|
||||
let g = pixel[1];
|
||||
let b = pixel[2];
|
||||
let a = u16::max_value();
|
||||
|
||||
local_data.extend_from_slice(&r.to_ne_bytes());
|
||||
local_data.extend_from_slice(&g.to_ne_bytes());
|
||||
local_data.extend_from_slice(&b.to_ne_bytes());
|
||||
local_data.extend_from_slice(&a.to_ne_bytes());
|
||||
data = i.into_raw();
|
||||
}
|
||||
DynamicImage::ImageLumaA8(i) => {
|
||||
let i = DynamicImage::ImageLumaA8(i).into_rgba8();
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = local_data;
|
||||
}
|
||||
DynamicImage::ImageRgba16(i) => {
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::Rgba16Uint;
|
||||
|
||||
let raw_data = i.into_raw();
|
||||
|
||||
data = cast_slice(&raw_data).to_owned();
|
||||
}
|
||||
DynamicImage::ImageRgb32F(image) => {
|
||||
width = image.width();
|
||||
height = image.height();
|
||||
format = TextureFormat::Rgba32Float;
|
||||
|
||||
let mut local_data =
|
||||
Vec::with_capacity(width as usize * height as usize * format.pixel_size());
|
||||
|
||||
for pixel in image.into_raw().chunks_exact(3) {
|
||||
// TODO: use the array_chunks method once stabilised
|
||||
// https://github.com/rust-lang/rust/issues/74985
|
||||
let r = pixel[0];
|
||||
let g = pixel[1];
|
||||
let b = pixel[2];
|
||||
let a = u16::max_value();
|
||||
|
||||
local_data.extend_from_slice(&r.to_ne_bytes());
|
||||
local_data.extend_from_slice(&g.to_ne_bytes());
|
||||
local_data.extend_from_slice(&b.to_ne_bytes());
|
||||
local_data.extend_from_slice(&a.to_ne_bytes());
|
||||
data = i.into_raw();
|
||||
}
|
||||
DynamicImage::ImageRgb8(i) => {
|
||||
let i = DynamicImage::ImageRgb8(i).into_rgba8();
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = local_data;
|
||||
data = i.into_raw();
|
||||
}
|
||||
DynamicImage::ImageRgba8(i) => {
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = if is_srgb {
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
TextureFormat::Rgba8Unorm
|
||||
};
|
||||
|
||||
data = i.into_raw();
|
||||
}
|
||||
DynamicImage::ImageLuma16(i) => {
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::R16Uint;
|
||||
|
||||
let raw_data = i.into_raw();
|
||||
|
||||
data = cast_slice(&raw_data).to_owned();
|
||||
}
|
||||
DynamicImage::ImageLumaA16(i) => {
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::Rg16Uint;
|
||||
|
||||
let raw_data = i.into_raw();
|
||||
|
||||
data = cast_slice(&raw_data).to_owned();
|
||||
}
|
||||
DynamicImage::ImageRgb16(image) => {
|
||||
width = image.width();
|
||||
height = image.height();
|
||||
format = TextureFormat::Rgba16Uint;
|
||||
|
||||
let mut local_data =
|
||||
Vec::with_capacity(width as usize * height as usize * format.pixel_size());
|
||||
|
||||
for pixel in image.into_raw().chunks_exact(3) {
|
||||
// TODO: use the array_chunks method once stabilised
|
||||
// https://github.com/rust-lang/rust/issues/74985
|
||||
let r = pixel[0];
|
||||
let g = pixel[1];
|
||||
let b = pixel[2];
|
||||
let a = u16::max_value();
|
||||
|
||||
local_data.extend_from_slice(&r.to_ne_bytes());
|
||||
local_data.extend_from_slice(&g.to_ne_bytes());
|
||||
local_data.extend_from_slice(&b.to_ne_bytes());
|
||||
local_data.extend_from_slice(&a.to_ne_bytes());
|
||||
}
|
||||
|
||||
data = local_data;
|
||||
}
|
||||
DynamicImage::ImageRgba16(i) => {
|
||||
width = i.width();
|
||||
height = i.height();
|
||||
format = TextureFormat::Rgba16Uint;
|
||||
|
||||
let raw_data = i.into_raw();
|
||||
|
||||
data = cast_slice(&raw_data).to_owned();
|
||||
}
|
||||
DynamicImage::ImageRgb32F(image) => {
|
||||
width = image.width();
|
||||
height = image.height();
|
||||
format = TextureFormat::Rgba32Float;
|
||||
|
||||
let mut local_data =
|
||||
Vec::with_capacity(width as usize * height as usize * format.pixel_size());
|
||||
|
||||
for pixel in image.into_raw().chunks_exact(3) {
|
||||
// TODO: use the array_chunks method once stabilised
|
||||
// https://github.com/rust-lang/rust/issues/74985
|
||||
let r = pixel[0];
|
||||
let g = pixel[1];
|
||||
let b = pixel[2];
|
||||
let a = u16::max_value();
|
||||
|
||||
local_data.extend_from_slice(&r.to_ne_bytes());
|
||||
local_data.extend_from_slice(&g.to_ne_bytes());
|
||||
local_data.extend_from_slice(&b.to_ne_bytes());
|
||||
local_data.extend_from_slice(&a.to_ne_bytes());
|
||||
}
|
||||
|
||||
data = local_data;
|
||||
}
|
||||
DynamicImage::ImageRgba32F(image) => {
|
||||
width = image.width();
|
||||
height = image.height();
|
||||
format = TextureFormat::Rgba32Float;
|
||||
|
||||
let raw_data = image.into_raw();
|
||||
|
||||
data = cast_slice(&raw_data).to_owned();
|
||||
}
|
||||
// DynamicImage is now non exhaustive, catch future variants and convert them
|
||||
_ => {
|
||||
let image = dyn_img.into_rgba8();
|
||||
width = image.width();
|
||||
height = image.height();
|
||||
format = TextureFormat::Rgba8UnormSrgb;
|
||||
|
||||
data = image.into_raw();
|
||||
}
|
||||
}
|
||||
DynamicImage::ImageRgba32F(image) => {
|
||||
width = image.width();
|
||||
height = image.height();
|
||||
format = TextureFormat::Rgba32Float;
|
||||
|
||||
let raw_data = image.into_raw();
|
||||
|
||||
data = cast_slice(&raw_data).to_owned();
|
||||
}
|
||||
// DynamicImage is now non exhaustive, catch future variants and convert them
|
||||
_ => {
|
||||
let image = dyn_img.into_rgba8();
|
||||
width = image.width();
|
||||
height = image.height();
|
||||
format = TextureFormat::Rgba8UnormSrgb;
|
||||
|
||||
data = image.into_raw();
|
||||
}
|
||||
Image::new(
|
||||
Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
TextureDimension::D2,
|
||||
data,
|
||||
format,
|
||||
)
|
||||
}
|
||||
|
||||
Image::new(
|
||||
Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
TextureDimension::D2,
|
||||
data,
|
||||
format,
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts an [`Image`] to a [`DynamicImage`]. Not all [`TextureFormat`] are
|
||||
/// covered, therefore it will return `None` if the format is unsupported.
|
||||
pub(crate) fn texture_to_image(texture: &Image) -> Option<DynamicImage> {
|
||||
match texture.texture_descriptor.format {
|
||||
TextureFormat::R8Unorm => ImageBuffer::from_raw(
|
||||
texture.texture_descriptor.size.width,
|
||||
texture.texture_descriptor.size.height,
|
||||
texture.data.clone(),
|
||||
)
|
||||
.map(DynamicImage::ImageLuma8),
|
||||
TextureFormat::Rg8Unorm => ImageBuffer::from_raw(
|
||||
texture.texture_descriptor.size.width,
|
||||
texture.texture_descriptor.size.height,
|
||||
texture.data.clone(),
|
||||
)
|
||||
.map(DynamicImage::ImageLumaA8),
|
||||
TextureFormat::Rgba8UnormSrgb => ImageBuffer::from_raw(
|
||||
texture.texture_descriptor.size.width,
|
||||
texture.texture_descriptor.size.height,
|
||||
texture.data.clone(),
|
||||
)
|
||||
.map(DynamicImage::ImageRgba8),
|
||||
_ => None,
|
||||
/// Convert a [`Image`] to a [`DynamicImage`]. Usefull for editing image
|
||||
/// data. Not all [`TextureFormat`] are covered, therefore it will return an
|
||||
/// error if the format is unsupported. Supported formats are:
|
||||
/// - `TextureFormat::R8Unorm`
|
||||
/// - `TextureFormat::Rg8Unorm`
|
||||
/// - `TextureFormat::Rgba8UnormSrgb`
|
||||
///
|
||||
/// To convert [`Image`] to a different format see: [`Image::convert`].
|
||||
pub fn try_into_dynamic(self) -> anyhow::Result<DynamicImage> {
|
||||
match self.texture_descriptor.format {
|
||||
TextureFormat::R8Unorm => ImageBuffer::from_raw(
|
||||
self.texture_descriptor.size.width,
|
||||
self.texture_descriptor.size.height,
|
||||
self.data,
|
||||
)
|
||||
.map(DynamicImage::ImageLuma8),
|
||||
TextureFormat::Rg8Unorm => ImageBuffer::from_raw(
|
||||
self.texture_descriptor.size.width,
|
||||
self.texture_descriptor.size.height,
|
||||
self.data,
|
||||
)
|
||||
.map(DynamicImage::ImageLumaA8),
|
||||
TextureFormat::Rgba8UnormSrgb => ImageBuffer::from_raw(
|
||||
self.texture_descriptor.size.width,
|
||||
self.texture_descriptor.size.height,
|
||||
self.data,
|
||||
)
|
||||
.map(DynamicImage::ImageRgba8),
|
||||
// Throw and error if conversion isn't supported
|
||||
texture_format => {
|
||||
return Err(anyhow!(
|
||||
"Conversion into dynamic image not supported for {:?}.",
|
||||
texture_format
|
||||
))
|
||||
}
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Failed to convert into {:?}.",
|
||||
self.texture_descriptor.format
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use image::{GenericImage, Rgba};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn two_way_conversion() {
|
||||
// Check to see if color is preserved through an rgba8 conversion and back.
|
||||
let mut initial = DynamicImage::new_rgba8(1, 1);
|
||||
initial.put_pixel(0, 0, Rgba::from([132, 3, 7, 200]));
|
||||
|
||||
let image = Image::from_dynamic(initial.clone(), true);
|
||||
|
||||
// NOTE: Fails if `is_srbg = false` or the dynamic image is of the type rgb8.
|
||||
assert_eq!(initial, image.try_into_dynamic().unwrap());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue