use std::result::Result as StdResult;
use gfx::{
format::{ChannelType, SurfaceType, SurfaceTyped},
texture::SamplerInfo,
traits::Pod,
};
use image::{DynamicImage, ImageFormat, RgbaImage};
use amethyst_assets::{
AssetStorage, Format, Handle, Loader, PrefabData, PrefabError, ProcessingState,
ProgressCounter, Result, ResultExt, SimpleFormat,
};
use amethyst_core::specs::prelude::{Entity, Read, ReadExpect};
use crate::{
tex::{FilterMethod, Texture, TextureBuilder},
types::SurfaceFormat,
Renderer,
};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TextureMetadata {
#[serde(default = "serde_helper::default_sampler")]
pub sampler: SamplerInfo,
#[serde(default = "serde_helper::default_mip_levels")]
pub mip_levels: u8,
#[serde(default)]
pub dynamic: bool,
#[serde(default = "SurfaceFormat::get_surface_type")]
pub format: SurfaceType,
#[serde(default)]
pub size: Option<(u16, u16)>,
pub channel: ChannelType,
}
impl TextureMetadata {
pub fn unorm() -> Self {
TextureMetadata {
sampler: serde_helper::default_sampler(),
mip_levels: serde_helper::default_mip_levels(),
dynamic: false,
format: SurfaceFormat::get_surface_type(),
size: None,
channel: ChannelType::Unorm,
}
}
pub fn srgb() -> Self {
TextureMetadata {
channel: ChannelType::Srgb,
..TextureMetadata::unorm()
}
}
pub fn srgb_scale() -> Self {
TextureMetadata::srgb().with_filter(FilterMethod::Scale)
}
pub fn with_sampler(mut self, info: SamplerInfo) -> Self {
self.sampler = info;
self
}
pub fn with_filter(mut self, filter: FilterMethod) -> Self {
self.sampler.filter = filter;
self
}
pub fn with_mip_levels(mut self, mip_levels: u8) -> Self {
self.mip_levels = mip_levels;
self
}
pub fn with_format(mut self, format: SurfaceType) -> Self {
self.format = format;
self
}
pub fn with_channel(mut self, channel: ChannelType) -> Self {
self.channel = channel;
self
}
pub fn with_size(mut self, width: u16, height: u16) -> Self {
self.size = Some((width, height));
self
}
pub fn dynamic(mut self, d: bool) -> Self {
self.dynamic = d;
self
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum TextureData {
#[serde(skip)]
Image(ImageData, TextureMetadata),
Rgba([f32; 4], TextureMetadata),
F32(Vec<f32>, TextureMetadata),
F64(Vec<f64>, TextureMetadata),
U8(Vec<u8>, TextureMetadata),
U16(Vec<u16>, TextureMetadata),
U32(Vec<u32>, TextureMetadata),
U64(Vec<u64>, TextureMetadata),
}
impl From<[f32; 4]> for TextureData {
fn from(color: [f32; 4]) -> Self {
TextureData::Rgba(color, TextureMetadata::srgb())
}
}
impl From<[f32; 3]> for TextureData {
fn from(color: [f32; 3]) -> Self {
[color[0], color[1], color[2], 1.0].into()
}
}
impl TextureData {
pub fn color(value: [f32; 4]) -> Self {
TextureData::Rgba(value, TextureMetadata::srgb())
}
}
impl<'a> PrefabData<'a> for TextureData {
type SystemData = (ReadExpect<'a, Loader>, Read<'a, AssetStorage<Texture>>);
type Result = Handle<Texture>;
fn add_to_entity(
&self,
_: Entity,
system_data: &mut Self::SystemData,
_: &[Entity],
) -> StdResult<Handle<Texture>, PrefabError> {
Ok(system_data
.0
.load_from_data(self.clone(), (), &system_data.1))
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum TexturePrefab<F>
where
F: Format<Texture, Options = TextureMetadata>,
{
Data(TextureData),
File(String, F, TextureMetadata),
#[serde(skip)]
Handle(Handle<Texture>),
}
impl<'a, F> PrefabData<'a> for TexturePrefab<F>
where
F: Format<Texture, Options = TextureMetadata> + Clone + Sync,
{
type SystemData = (ReadExpect<'a, Loader>, Read<'a, AssetStorage<Texture>>);
type Result = Handle<Texture>;
fn add_to_entity(
&self,
_: Entity,
system_data: &mut Self::SystemData,
_: &[Entity],
) -> StdResult<Handle<Texture>, PrefabError> {
let handle = match *self {
TexturePrefab::Data(ref data) => {
system_data
.0
.load_from_data(data.clone(), (), &system_data.1)
}
TexturePrefab::File(..) => unreachable!(),
TexturePrefab::Handle(ref handle) => handle.clone(),
};
Ok(handle)
}
fn load_sub_assets(
&mut self,
progress: &mut ProgressCounter,
system_data: &mut Self::SystemData,
) -> StdResult<bool, PrefabError> {
let handle = match *self {
TexturePrefab::Data(ref data) => Some(system_data.0.load_from_data(
data.clone(),
progress,
&system_data.1,
)),
TexturePrefab::File(ref name, ref format, ref options) => Some(system_data.0.load(
name.as_ref(),
format.clone(),
options.clone(),
progress,
&system_data.1,
)),
TexturePrefab::Handle(_) => None,
};
if let Some(handle) = handle {
*self = TexturePrefab::Handle(handle);
Ok(true)
} else {
Ok(false)
}
}
}
#[derive(Clone, Debug)]
pub struct ImageData {
pub rgba: RgbaImage,
}
fn load_into_rgba8_from_memory(
data: &[u8],
options: TextureMetadata,
format: ImageFormat,
) -> Result<TextureData> {
use image::load_from_memory_with_format;
load_from_memory_with_format(data, format)
.map(|image| {
match image {
DynamicImage::ImageRgba8(im) => im,
_ => {
image.to_rgba()
}
}
})
.map(|rgba| TextureData::Image(ImageData { rgba }, options))
.chain_err(|| "Image decoding failed")
}
#[derive(Clone, Deserialize, Serialize)]
pub struct JpgFormat;
impl JpgFormat {
pub fn from_data(data: &[u8], options: TextureMetadata) -> Result<TextureData> {
load_into_rgba8_from_memory(data, options, ImageFormat::JPEG)
}
}
impl SimpleFormat<Texture> for JpgFormat {
const NAME: &'static str = "JPEG";
type Options = TextureMetadata;
fn import(&self, bytes: Vec<u8>, options: TextureMetadata) -> Result<TextureData> {
JpgFormat::from_data(&bytes, options)
}
}
#[derive(Clone, Deserialize, Serialize)]
pub struct PngFormat;
impl PngFormat {
pub fn from_data(data: &[u8], options: TextureMetadata) -> Result<TextureData> {
load_into_rgba8_from_memory(data, options, ImageFormat::PNG)
}
}
impl SimpleFormat<Texture> for PngFormat {
const NAME: &'static str = "PNG";
type Options = TextureMetadata;
fn import(&self, bytes: Vec<u8>, options: TextureMetadata) -> Result<TextureData> {
PngFormat::from_data(&bytes, options)
}
}
#[derive(Clone, Deserialize, Serialize)]
pub struct BmpFormat;
impl SimpleFormat<Texture> for BmpFormat {
const NAME: &'static str = "BMP";
type Options = TextureMetadata;
fn import(&self, bytes: Vec<u8>, options: TextureMetadata) -> Result<TextureData> {
load_into_rgba8_from_memory(&bytes, options, ImageFormat::BMP)
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TgaFormat;
impl TgaFormat {
pub fn from_data(data: &[u8], options: TextureMetadata) -> Result<TextureData> {
load_into_rgba8_from_memory(data, options, ImageFormat::TGA)
}
}
impl SimpleFormat<Texture> for TgaFormat {
const NAME: &'static str = "TGA";
type Options = TextureMetadata;
fn import(&self, bytes: Vec<u8>, options: TextureMetadata) -> Result<TextureData> {
TgaFormat::from_data(&bytes, options)
}
}
pub fn create_texture_asset(
data: TextureData,
renderer: &mut Renderer,
) -> Result<ProcessingState<Texture>> {
use self::TextureData::*;
let t = match data {
Image(image_data, options) => {
create_texture_asset_from_image(image_data, options, renderer)
}
Rgba(color, options) => {
let tb = apply_options(Texture::from_color_val(color), options);
renderer
.create_texture(tb)
.chain_err(|| "Failed to build texture")
}
F32(data, options) => {
let tb = apply_options(TextureBuilder::new(data), options);
renderer
.create_texture(tb)
.chain_err(|| "Failed to build texture")
}
F64(data, options) => {
let tb = apply_options(TextureBuilder::new(data), options);
renderer
.create_texture(tb)
.chain_err(|| "Failed to build texture")
}
U8(data, options) => {
let tb = apply_options(TextureBuilder::new(data), options);
renderer
.create_texture(tb)
.chain_err(|| "Failed to build texture")
}
U16(data, options) => {
let tb = apply_options(TextureBuilder::new(data), options);
renderer
.create_texture(tb)
.chain_err(|| "Failed to build texture")
}
U32(data, options) => {
let tb = apply_options(TextureBuilder::new(data), options);
renderer
.create_texture(tb)
.chain_err(|| "Failed to build texture")
}
U64(data, options) => {
let tb = apply_options(TextureBuilder::new(data), options);
renderer
.create_texture(tb)
.chain_err(|| "Failed to build texture")
}
};
t.map(ProcessingState::Loaded)
}
fn apply_options<D, T>(tb: TextureBuilder<D, T>, metadata: TextureMetadata) -> TextureBuilder<D, T>
where
D: AsRef<[T]>,
T: Pod + Copy,
{
let builder = tb
.with_sampler(metadata.sampler)
.mip_levels(metadata.mip_levels)
.dynamic(metadata.dynamic)
.with_format(metadata.format)
.with_channel_type(metadata.channel);
if let Some((x, y)) = metadata.size {
builder.with_size(x, y)
} else {
builder
}
}
fn create_texture_asset_from_image(
image: ImageData,
options: TextureMetadata,
renderer: &mut Renderer,
) -> Result<Texture> {
let fmt = SurfaceType::R8_G8_B8_A8;
let chan = options.channel;
let rgba = image.rgba;
let w = rgba.width();
let h = rgba.height();
if w > u32::from(u16::max_value()) || h > u32::from(u16::max_value()) {
bail!(
"Unsupported texture size (expected: ({}, {}), got: ({}, {})",
u16::max_value(),
u16::max_value(),
w,
h
);
}
let tb = apply_options(
TextureBuilder::new(rgba.into_raw())
.with_format(fmt)
.with_channel_type(chan)
.with_size(w as u16, h as u16),
options,
);
renderer
.create_texture(tb)
.chain_err(|| "Failed to create texture from texture data")
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum TextureFormat {
Jpg,
Png,
Bmp,
Tga,
}
impl SimpleFormat<Texture> for TextureFormat {
const NAME: &'static str = "TextureFormat";
type Options = TextureMetadata;
fn import(&self, bytes: Vec<u8>, options: TextureMetadata) -> Result<TextureData> {
match *self {
TextureFormat::Jpg => SimpleFormat::import(&JpgFormat, bytes, options),
TextureFormat::Png => SimpleFormat::import(&PngFormat, bytes, options),
TextureFormat::Bmp => SimpleFormat::import(&BmpFormat, bytes, options),
TextureFormat::Tga => SimpleFormat::import(&TgaFormat, bytes, options),
}
}
}
mod serde_helper {
use crate::tex::{FilterMethod, WrapMode};
use super::SamplerInfo;
fn default_filter() -> FilterMethod {
FilterMethod::Trilinear
}
fn default_wrap() -> WrapMode {
WrapMode::Clamp
}
pub fn default_sampler() -> SamplerInfo {
SamplerInfo::new(default_filter(), default_wrap())
}
pub fn default_mip_levels() -> u8 {
1
}
}
#[cfg(test)]
mod tests {
use super::TextureData;
#[test]
fn texture_data_from_f32_3() {
match TextureData::from([0.25, 0.50, 0.75]) {
TextureData::Rgba(color, _) => {
assert_eq!(color, [0.25, 0.50, 0.75, 1.0]);
}
_ => panic!("Expected [f32; 3] to turn into TextureData::Rgba"),
}
}
}