Expose Image conversion functions (fixes #5452) (#5527)

## 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:
micron-mushroom 2022-09-03 17:47:38 +00:00
parent 6a54683491
commit cbb884cb02
2 changed files with 226 additions and 185 deletions

View File

@ -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))
}
}
}

View File

@ -1,10 +1,11 @@
use crate::texture::{Image, TextureFormatPixelInfo};
use anyhow::anyhow;
use image::{DynamicImage, ImageBuffer};
use wgpu::{Extent3d, TextureDimension, TextureFormat};
// TODO: fix name?
impl Image {
/// Converts a [`DynamicImage`] to an [`Image`].
pub(crate) fn image_to_texture(dyn_img: DynamicImage, is_srgb: bool) -> Image {
pub fn from_dynamic(dyn_img: DynamicImage, is_srgb: bool) -> Image {
use bevy_core::cast_slice;
let width;
let height;
@ -167,28 +168,66 @@ pub(crate) fn image_to_texture(dyn_img: DynamicImage, is_srgb: bool) -> Image {
)
}
/// 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 {
/// 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(
texture.texture_descriptor.size.width,
texture.texture_descriptor.size.height,
texture.data.clone(),
self.texture_descriptor.size.width,
self.texture_descriptor.size.height,
self.data,
)
.map(DynamicImage::ImageLuma8),
TextureFormat::Rg8Unorm => ImageBuffer::from_raw(
texture.texture_descriptor.size.width,
texture.texture_descriptor.size.height,
texture.data.clone(),
self.texture_descriptor.size.width,
self.texture_descriptor.size.height,
self.data,
)
.map(DynamicImage::ImageLumaA8),
TextureFormat::Rgba8UnormSrgb => ImageBuffer::from_raw(
texture.texture_descriptor.size.width,
texture.texture_descriptor.size.height,
texture.data.clone(),
self.texture_descriptor.size.width,
self.texture_descriptor.size.height,
self.data,
)
.map(DynamicImage::ImageRgba8),
_ => None,
// 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());
}
}