diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 4561c63c6b..2d28a67e91 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -20,9 +20,7 @@ bevy_window = { path = "../bevy_window" } # rendering spirv-reflect = "0.2.3" glsl-to-spirv = { git = "https://github.com/cart/glsl-to-spirv" } -# TODO: move this to its own crate -png = "0.16.0" -image = "0.23" +image = { version = "0.23", default-features = false, features = ["png", "hdr"] } # misc log = { version = "0.4", features = ["release_max_level_info"] } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 4625136be6..053bfe05eb 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -38,7 +38,7 @@ use render_graph::{ }; use renderer::{AssetRenderResourceBindings, RenderResourceBindings}; use std::ops::Range; -use texture::{HdrTextureLoader, PngTextureLoader, TextureResourceSystemState}; +use texture::{HdrTextureLoader, ImageTextureLoader, TextureResourceSystemState}; pub mod stage { /// Stage where render resources are set up @@ -76,7 +76,7 @@ impl AppPlugin for RenderPlugin { .add_asset::() .add_asset::() .add_asset_loader::() - .add_asset_loader::() + .add_asset_loader::() .register_component::() .register_component::() .register_component::() diff --git a/crates/bevy_render/src/texture/image_texture_loader.rs b/crates/bevy_render/src/texture/image_texture_loader.rs new file mode 100644 index 0000000000..0937a3741d --- /dev/null +++ b/crates/bevy_render/src/texture/image_texture_loader.rs @@ -0,0 +1,155 @@ +use super::{Texture, TextureFormat}; +use anyhow::Result; +use bevy_asset::AssetLoader; +use bevy_math::Vec2; +use std::path::Path; + +/// Loader for images that can be read by the `image` crate. +/// +/// Reads only PNG images for now. +#[derive(Clone, Default)] +pub struct ImageTextureLoader; + +impl AssetLoader for ImageTextureLoader { + fn from_bytes(&self, asset_path: &Path, bytes: Vec) -> Result { + use bevy_core::AsBytes; + + // Find the image type we expect. A file with the extension "png" should + // probably load as a PNG. + + let ext = asset_path.extension().unwrap().to_str().unwrap(); + + // NOTE: If more formats are added they can be added here. + let img_format = if ext.eq_ignore_ascii_case("png") { + image::ImageFormat::Png + } else { + panic!( + "Unexpected image format {:?} for file {}, this is an error in `bevy_render`.", + ext, + asset_path.display() + ) + }; + + // Load the image in the expected format. + // Some formats like PNG allow for R or RG textures too, so the texture + // format needs to be determined. For RGB textures an alpha channel + // needs to be added, so the image data needs to be converted in those + // cases. + + let dyn_img = image::load_from_memory_with_format(bytes.as_slice(), img_format)?; + + let width; + let height; + + let data: Vec; + let format: TextureFormat; + + match dyn_img { + image::DynamicImage::ImageLuma8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::R8Unorm; + + data = i.into_raw(); + } + image::DynamicImage::ImageLumaA8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rg8Unorm; + + data = i.into_raw(); + } + image::DynamicImage::ImageRgb8(i) => { + let i = image::DynamicImage::ImageRgb8(i).into_rgba(); + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba8UnormSrgb; + + data = i.into_raw(); + } + image::DynamicImage::ImageRgba8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba8UnormSrgb; + + data = i.into_raw(); + } + image::DynamicImage::ImageBgr8(i) => { + let i = image::DynamicImage::ImageBgr8(i).into_bgra(); + + width = i.width(); + height = i.height(); + format = TextureFormat::Bgra8UnormSrgb; + + data = i.into_raw(); + } + image::DynamicImage::ImageBgra8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Bgra8UnormSrgb; + + data = i.into_raw(); + } + image::DynamicImage::ImageLuma16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::R16Uint; + + let raw_data = i.into_raw(); + + data = raw_data.as_slice().as_bytes().to_owned(); + } + image::DynamicImage::ImageLumaA16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rg16Uint; + + let raw_data = i.into_raw(); + + data = raw_data.as_slice().as_bytes().to_owned(); + } + image::DynamicImage::ImageRgb16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba16Uint; + + let mut d = + Vec::with_capacity(width as usize * height as usize * format.pixel_size()); + + for pixel in i.into_raw().chunks_exact(3) { + // TODO unsafe_get in release builds? + let r = pixel[0]; + let g = pixel[1]; + let b = pixel[2]; + let a = u16::max_value(); + + d.extend_from_slice(&r.to_ne_bytes()); + d.extend_from_slice(&g.to_ne_bytes()); + d.extend_from_slice(&b.to_ne_bytes()); + d.extend_from_slice(&a.to_ne_bytes()); + } + + data = d; + } + image::DynamicImage::ImageRgba16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba16Uint; + + let raw_data = i.into_raw(); + + data = raw_data.as_slice().as_bytes().to_owned(); + } + } + + Ok(Texture::new( + Vec2::new(width as f32, height as f32), + data, + format, + )) + } + fn extensions(&self) -> &[&str] { + static EXTENSIONS: &[&str] = &["png"]; + EXTENSIONS + } +} diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index a3eb70b7fc..e6ab2a793e 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -1,12 +1,12 @@ mod hdr_texture_loader; -mod png_texture_loader; +mod image_texture_loader; mod sampler_descriptor; mod texture; mod texture_descriptor; mod texture_dimension; pub use hdr_texture_loader::*; -pub use png_texture_loader::*; +pub use image_texture_loader::*; pub use sampler_descriptor::*; pub use texture::*; pub use texture_descriptor::*; diff --git a/crates/bevy_render/src/texture/png_texture_loader.rs b/crates/bevy_render/src/texture/png_texture_loader.rs deleted file mode 100644 index daeb9ec321..0000000000 --- a/crates/bevy_render/src/texture/png_texture_loader.rs +++ /dev/null @@ -1,26 +0,0 @@ -use super::{Texture, TextureFormat}; -use anyhow::Result; -use bevy_asset::AssetLoader; -use bevy_math::Vec2; -use std::path::Path; - -#[derive(Clone, Default)] -pub struct PngTextureLoader; - -impl AssetLoader for PngTextureLoader { - fn from_bytes(&self, _asset_path: &Path, bytes: Vec) -> Result { - let decoder = png::Decoder::new(bytes.as_slice()); - let (info, mut reader) = decoder.read_info()?; - let mut data = vec![0; info.buffer_size()]; - reader.next_frame(&mut data)?; - Ok(Texture::new( - Vec2::new(info.width as f32, info.height as f32), - data, - TextureFormat::Rgba8UnormSrgb, - )) - } - fn extensions(&self) -> &[&str] { - static EXTENSIONS: &[&str] = &["png"]; - EXTENSIONS - } -}