Path: blob/main/crates/bevy_image/src/image_texture_conversion.rs
6595 views
use crate::{Image, TextureFormatPixelInfo};1use bevy_asset::RenderAssetUsages;2use image::{DynamicImage, ImageBuffer};3use thiserror::Error;4use wgpu_types::{Extent3d, TextureDimension, TextureFormat};56impl Image {7/// Converts a [`DynamicImage`] to an [`Image`].8pub fn from_dynamic(9dyn_img: DynamicImage,10is_srgb: bool,11asset_usage: RenderAssetUsages,12) -> Image {13use bytemuck::cast_slice;14let width;15let height;1617let data: Vec<u8>;18let format: TextureFormat;1920match dyn_img {21DynamicImage::ImageLuma8(image) => {22let i = DynamicImage::ImageLuma8(image).into_rgba8();23width = i.width();24height = i.height();25format = if is_srgb {26TextureFormat::Rgba8UnormSrgb27} else {28TextureFormat::Rgba8Unorm29};3031data = i.into_raw();32}33DynamicImage::ImageLumaA8(image) => {34let i = DynamicImage::ImageLumaA8(image).into_rgba8();35width = i.width();36height = i.height();37format = if is_srgb {38TextureFormat::Rgba8UnormSrgb39} else {40TextureFormat::Rgba8Unorm41};4243data = i.into_raw();44}45DynamicImage::ImageRgb8(image) => {46let i = DynamicImage::ImageRgb8(image).into_rgba8();47width = i.width();48height = i.height();49format = if is_srgb {50TextureFormat::Rgba8UnormSrgb51} else {52TextureFormat::Rgba8Unorm53};5455data = i.into_raw();56}57DynamicImage::ImageRgba8(image) => {58width = image.width();59height = image.height();60format = if is_srgb {61TextureFormat::Rgba8UnormSrgb62} else {63TextureFormat::Rgba8Unorm64};6566data = image.into_raw();67}68DynamicImage::ImageLuma16(image) => {69width = image.width();70height = image.height();71format = TextureFormat::R16Uint;7273let raw_data = image.into_raw();7475data = cast_slice(&raw_data).to_owned();76}77DynamicImage::ImageLumaA16(image) => {78width = image.width();79height = image.height();80format = TextureFormat::Rg16Uint;8182let raw_data = image.into_raw();8384data = cast_slice(&raw_data).to_owned();85}86DynamicImage::ImageRgb16(image) => {87let i = DynamicImage::ImageRgb16(image).into_rgba16();88width = i.width();89height = i.height();90format = TextureFormat::Rgba16Unorm;9192let raw_data = i.into_raw();9394data = cast_slice(&raw_data).to_owned();95}96DynamicImage::ImageRgba16(image) => {97width = image.width();98height = image.height();99format = TextureFormat::Rgba16Unorm;100101let raw_data = image.into_raw();102103data = cast_slice(&raw_data).to_owned();104}105DynamicImage::ImageRgb32F(image) => {106width = image.width();107height = image.height();108format = TextureFormat::Rgba32Float;109110let mut local_data = Vec::with_capacity(111width as usize * height as usize * format.pixel_size().unwrap_or(0),112);113114for pixel in image.into_raw().chunks_exact(3) {115// TODO: use the array_chunks method once stabilized116// https://github.com/rust-lang/rust/issues/74985117let r = pixel[0];118let g = pixel[1];119let b = pixel[2];120let a = 1f32;121122local_data.extend_from_slice(&r.to_le_bytes());123local_data.extend_from_slice(&g.to_le_bytes());124local_data.extend_from_slice(&b.to_le_bytes());125local_data.extend_from_slice(&a.to_le_bytes());126}127128data = local_data;129}130DynamicImage::ImageRgba32F(image) => {131width = image.width();132height = image.height();133format = TextureFormat::Rgba32Float;134135let raw_data = image.into_raw();136137data = cast_slice(&raw_data).to_owned();138}139// DynamicImage is now non exhaustive, catch future variants and convert them140_ => {141let image = dyn_img.into_rgba8();142width = image.width();143height = image.height();144format = TextureFormat::Rgba8UnormSrgb;145146data = image.into_raw();147}148}149150Image::new(151Extent3d {152width,153height,154depth_or_array_layers: 1,155},156TextureDimension::D2,157data,158format,159asset_usage,160)161}162163/// Convert a [`Image`] to a [`DynamicImage`]. Useful for editing image164/// data. Not all [`TextureFormat`] are covered, therefore it will return an165/// error if the format is unsupported. Supported formats are:166/// - `TextureFormat::R8Unorm`167/// - `TextureFormat::Rg8Unorm`168/// - `TextureFormat::Rgba8UnormSrgb`169/// - `TextureFormat::Bgra8UnormSrgb`170///171/// To convert [`Image`] to a different format see: [`Image::convert`].172pub fn try_into_dynamic(self) -> Result<DynamicImage, IntoDynamicImageError> {173let width = self.width();174let height = self.height();175let Some(data) = self.data else {176return Err(IntoDynamicImageError::UninitializedImage);177};178match self.texture_descriptor.format {179TextureFormat::R8Unorm => {180ImageBuffer::from_raw(width, height, data).map(DynamicImage::ImageLuma8)181}182TextureFormat::Rg8Unorm => {183ImageBuffer::from_raw(width, height, data).map(DynamicImage::ImageLumaA8)184}185TextureFormat::Rgba8UnormSrgb => {186ImageBuffer::from_raw(width, height, data).map(DynamicImage::ImageRgba8)187}188// This format is commonly used as the format for the swapchain texture189// This conversion is added here to support screenshots190TextureFormat::Bgra8UnormSrgb | TextureFormat::Bgra8Unorm => {191ImageBuffer::from_raw(width, height, {192let mut data = data;193for bgra in data.chunks_exact_mut(4) {194bgra.swap(0, 2);195}196data197})198.map(DynamicImage::ImageRgba8)199}200// Throw and error if conversion isn't supported201texture_format => return Err(IntoDynamicImageError::UnsupportedFormat(texture_format)),202}203.ok_or(IntoDynamicImageError::UnknownConversionError(204self.texture_descriptor.format,205))206}207}208209/// Errors that occur while converting an [`Image`] into a [`DynamicImage`]210#[non_exhaustive]211#[derive(Error, Debug)]212pub enum IntoDynamicImageError {213/// Conversion into dynamic image not supported for source format.214#[error("Conversion into dynamic image not supported for {0:?}.")]215UnsupportedFormat(TextureFormat),216217/// Encountered an unknown error during conversion.218#[error("Failed to convert into {0:?}.")]219UnknownConversionError(TextureFormat),220221/// Tried to convert an image that has no texture data222#[error("Image has no texture data")]223UninitializedImage,224}225226#[cfg(test)]227mod test {228use image::{GenericImage, Rgba};229230use super::*;231232#[test]233fn two_way_conversion() {234// Check to see if color is preserved through an rgba8 conversion and back.235let mut initial = DynamicImage::new_rgba8(1, 1);236initial.put_pixel(0, 0, Rgba::from([132, 3, 7, 200]));237238let image = Image::from_dynamic(initial.clone(), true, RenderAssetUsages::RENDER_WORLD);239240// NOTE: Fails if `is_srgb = false` or the dynamic image is of the type rgb8.241assert_eq!(initial, image.try_into_dynamic().unwrap());242}243}244245246