add texture loader for more formats using image crate

This adds support for PNG images only for now. More formats can be added
relatively easily.
Images with various pixel formats are supported (such as RGB-16bit or
R-8bit).
This commit is contained in:
Thomas Herzog 2020-07-27 20:41:41 +02:00
parent 7412b0ec25
commit 23149f1753
5 changed files with 160 additions and 33 deletions

View file

@ -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"] }

View file

@ -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::<Shader>()
.add_asset::<PipelineDescriptor>()
.add_asset_loader::<Texture, HdrTextureLoader>()
.add_asset_loader::<Texture, PngTextureLoader>()
.add_asset_loader::<Texture, ImageTextureLoader>()
.register_component::<Camera>()
.register_component::<Draw>()
.register_component::<RenderPipelines>()

View file

@ -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<Texture> for ImageTextureLoader {
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<Texture> {
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<u8>;
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
}
}

View file

@ -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::*;

View file

@ -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<Texture> for PngTextureLoader {
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<Texture> {
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
}
}