use crate::{
asset::{define_new_resource, Resource, ResourceData, ResourceState},
core::{
futures::io::Error,
inspect::{Inspect, PropertyInfo},
io::{self, FileLoadError},
visitor::{PodVecView, Visit, VisitError, VisitResult, Visitor},
},
engine::resource_manager::ImportOptions,
};
use ddsfile::{Caps2, D3DFormat};
use fxhash::FxHasher;
use image::{
imageops::FilterType, ColorType, DynamicImage, GenericImageView, ImageError, ImageFormat,
};
use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
io::Cursor,
ops::{Deref, DerefMut},
path::{Path, PathBuf},
};
use strum_macros::{AsRefStr, EnumString, EnumVariantNames};
#[derive(Copy, Clone, Debug)]
pub enum TextureKind {
Line {
length: u32,
},
Rectangle {
width: u32,
height: u32,
},
Cube {
width: u32,
height: u32,
},
Volume {
width: u32,
height: u32,
depth: u32,
},
}
impl Default for TextureKind {
fn default() -> Self {
Self::Rectangle {
width: 0,
height: 0,
}
}
}
impl Visit for TextureKind {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
visitor.enter_region(name)?;
let mut id = match self {
TextureKind::Line { .. } => 0,
TextureKind::Rectangle { .. } => 1,
TextureKind::Cube { .. } => 2,
TextureKind::Volume { .. } => 3,
};
id.visit("Id", visitor)?;
if visitor.is_reading() {
*self = match id {
0 => TextureKind::Line { length: 0 },
1 => TextureKind::Rectangle {
width: 0,
height: 0,
},
2 => TextureKind::Cube {
width: 0,
height: 0,
},
3 => TextureKind::Volume {
width: 0,
height: 0,
depth: 0,
},
_ => {
return VisitResult::Err(VisitError::User(format!(
"Invalid texture kind {}!",
id
)))
}
};
}
match self {
TextureKind::Line { length } => {
length.visit("Length", visitor)?;
}
TextureKind::Rectangle { width, height } => {
width.visit("Width", visitor)?;
height.visit("Height", visitor)?;
}
TextureKind::Cube { width, height } => {
width.visit("Width", visitor)?;
height.visit("Height", visitor)?;
}
TextureKind::Volume {
width,
height,
depth,
} => {
width.visit("Width", visitor)?;
height.visit("Height", visitor)?;
depth.visit("Depth", visitor)?;
}
}
visitor.leave_region()
}
}
#[derive(Default, Clone)]
struct TextureBytes(Vec<u8>);
impl Visit for TextureBytes {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
self.0.visit(name, visitor)
}
}
impl Debug for TextureBytes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Texture has {} bytes", self.0.len())
}
}
impl From<Vec<u8>> for TextureBytes {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl Deref for TextureBytes {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for TextureBytes {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Debug)]
pub struct TextureData {
path: PathBuf,
kind: TextureKind,
bytes: TextureBytes,
pixel_kind: TexturePixelKind,
minification_filter: TextureMinificationFilter,
magnification_filter: TextureMagnificationFilter,
s_wrap_mode: TextureWrapMode,
t_wrap_mode: TextureWrapMode,
mip_count: u32,
anisotropy: f32,
serialize_content: bool,
data_hash: u64,
is_render_target: bool,
}
impl ResourceData for TextureData {
fn path(&self) -> Cow<Path> {
Cow::Borrowed(&self.path)
}
fn set_path(&mut self, path: PathBuf) {
self.path = path;
}
}
impl Visit for TextureData {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
visitor.enter_region(name)?;
let mut kind = self.pixel_kind.id();
kind.visit("KindId", visitor)?;
if visitor.is_reading() {
self.pixel_kind = TexturePixelKind::new(kind)?;
}
self.path.visit("Path", visitor)?;
self.minification_filter
.visit("MinificationFilter", visitor)?;
self.magnification_filter
.visit("MagnificationFilter", visitor)?;
self.anisotropy.visit("Anisotropy", visitor)?;
self.s_wrap_mode.visit("SWrapMode", visitor)?;
self.t_wrap_mode.visit("TWrapMode", visitor)?;
self.mip_count.visit("MipCount", visitor)?;
self.kind.visit("Kind", visitor)?;
let _ = self.serialize_content.visit("SerializeContent", visitor);
if self.serialize_content {
let mut bytes_view = PodVecView::from_pod_vec(&mut self.bytes);
bytes_view.visit("Data", visitor)?;
}
visitor.leave_region()
}
}
impl Default for TextureData {
fn default() -> Self {
Self {
path: PathBuf::new(),
kind: TextureKind::Rectangle {
width: 0,
height: 0,
},
bytes: Default::default(),
pixel_kind: TexturePixelKind::RGBA8,
minification_filter: TextureMinificationFilter::LinearMipMapLinear,
magnification_filter: TextureMagnificationFilter::Linear,
s_wrap_mode: TextureWrapMode::Repeat,
t_wrap_mode: TextureWrapMode::Repeat,
mip_count: 1,
anisotropy: 16.0,
serialize_content: false,
data_hash: 0,
is_render_target: false,
}
}
}
#[derive(Clone, Deserialize, Serialize, Inspect)]
pub struct TextureImportOptions {
#[serde(default)]
pub(crate) minification_filter: TextureMinificationFilter,
#[serde(default)]
pub(crate) magnification_filter: TextureMagnificationFilter,
#[serde(default)]
pub(crate) s_wrap_mode: TextureWrapMode,
#[serde(default)]
pub(crate) t_wrap_mode: TextureWrapMode,
#[serde(default)]
pub(crate) anisotropy: f32,
#[serde(default)]
pub(crate) compression: CompressionOptions,
}
impl Default for TextureImportOptions {
fn default() -> Self {
Self {
minification_filter: TextureMinificationFilter::LinearMipMapLinear,
magnification_filter: TextureMagnificationFilter::Linear,
s_wrap_mode: TextureWrapMode::Repeat,
t_wrap_mode: TextureWrapMode::Repeat,
anisotropy: 16.0,
compression: CompressionOptions::Quality,
}
}
}
impl ImportOptions for TextureImportOptions {}
impl TextureImportOptions {
pub fn with_minification_filter(
mut self,
minification_filter: TextureMinificationFilter,
) -> Self {
self.minification_filter = minification_filter;
self
}
pub fn set_minification_filter(&mut self, minification_filter: TextureMinificationFilter) {
self.minification_filter = minification_filter;
}
pub fn with_magnification_filter(
mut self,
magnification_filter: TextureMagnificationFilter,
) -> Self {
self.magnification_filter = magnification_filter;
self
}
pub fn set_magnification_filter(&mut self, magnification_filter: TextureMagnificationFilter) {
self.magnification_filter = magnification_filter;
}
pub fn with_s_wrap_mode(mut self, s_wrap_mode: TextureWrapMode) -> Self {
self.s_wrap_mode = s_wrap_mode;
self
}
pub fn set_s_wrap_mode(&mut self, s_wrap_mode: TextureWrapMode) {
self.s_wrap_mode = s_wrap_mode;
}
pub fn with_t_wrap_mode(mut self, t_wrap_mode: TextureWrapMode) -> Self {
self.t_wrap_mode = t_wrap_mode;
self
}
pub fn set_t_wrap_mode(&mut self, t_wrap_mode: TextureWrapMode) {
self.t_wrap_mode = t_wrap_mode;
}
pub fn with_anisotropy(mut self, anisotropy: f32) -> Self {
self.anisotropy = anisotropy.min(1.0);
self
}
pub fn set_anisotropy(&mut self, anisotropy: f32) {
self.anisotropy = anisotropy.min(1.0);
}
pub fn with_compression(mut self, compression: CompressionOptions) -> Self {
self.compression = compression;
self
}
pub fn set_compression(&mut self, compression: CompressionOptions) {
self.compression = compression;
}
}
define_new_resource!(
Texture<TextureData, TextureError>
);
pub type TextureState = ResourceState<TextureData, TextureError>;
impl Texture {
pub fn new_render_target(width: u32, height: u32) -> Self {
Self(Resource::new(TextureState::Ok(TextureData {
path: Default::default(),
kind: TextureKind::Rectangle { width, height },
bytes: Default::default(),
pixel_kind: TexturePixelKind::RGBA8,
minification_filter: TextureMinificationFilter::Linear,
magnification_filter: TextureMagnificationFilter::Linear,
s_wrap_mode: TextureWrapMode::Repeat,
t_wrap_mode: TextureWrapMode::Repeat,
mip_count: 1,
anisotropy: 1.0,
serialize_content: false,
data_hash: 0,
is_render_target: true,
})))
}
pub fn load_from_memory(
data: &[u8],
compression: CompressionOptions,
gen_mip_maps: bool,
) -> Result<Self, TextureError> {
Ok(Self(Resource::new(TextureState::Ok(
TextureData::load_from_memory(data, compression, gen_mip_maps)?,
))))
}
pub fn from_bytes(
kind: TextureKind,
pixel_kind: TexturePixelKind,
bytes: Vec<u8>,
serialize_content: bool,
) -> Option<Self> {
Some(Self(Resource::new(TextureState::Ok(
TextureData::from_bytes(kind, pixel_kind, bytes, serialize_content)?,
))))
}
}
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialOrd,
PartialEq,
Deserialize,
Serialize,
Inspect,
EnumVariantNames,
EnumString,
AsRefStr,
)]
#[repr(u32)]
pub enum TextureMagnificationFilter {
Nearest = 0,
Linear = 1,
}
impl Default for TextureMagnificationFilter {
fn default() -> Self {
Self::Linear
}
}
impl Visit for TextureMagnificationFilter {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
visitor.enter_region(name)?;
let mut id = *self as u32;
id.visit("Id", visitor)?;
if visitor.is_reading() {
*self = match id {
0 => TextureMagnificationFilter::Nearest,
1 => TextureMagnificationFilter::Linear,
_ => {
return VisitResult::Err(VisitError::User(format!(
"Invalid magnification filter {}!",
id
)))
}
}
}
visitor.leave_region()
}
}
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialOrd,
PartialEq,
Deserialize,
Serialize,
Inspect,
EnumVariantNames,
EnumString,
AsRefStr,
)]
#[repr(u32)]
pub enum TextureMinificationFilter {
Nearest = 0,
NearestMipMapNearest = 1,
NearestMipMapLinear = 2,
Linear = 3,
LinearMipMapNearest = 4,
LinearMipMapLinear = 5,
}
impl TextureMinificationFilter {
pub fn is_using_mip_mapping(self) -> bool {
match self {
TextureMinificationFilter::Nearest | TextureMinificationFilter::Linear => false,
TextureMinificationFilter::NearestMipMapNearest
| TextureMinificationFilter::LinearMipMapLinear
| TextureMinificationFilter::NearestMipMapLinear
| TextureMinificationFilter::LinearMipMapNearest => true,
}
}
}
impl Default for TextureMinificationFilter {
fn default() -> Self {
Self::LinearMipMapLinear
}
}
impl Visit for TextureMinificationFilter {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
visitor.enter_region(name)?;
let mut id = *self as u32;
id.visit("Id", visitor)?;
if visitor.is_reading() {
*self = match id {
0 => TextureMinificationFilter::Nearest,
1 => TextureMinificationFilter::NearestMipMapNearest,
2 => TextureMinificationFilter::NearestMipMapLinear,
3 => TextureMinificationFilter::Linear,
4 => TextureMinificationFilter::LinearMipMapNearest,
5 => TextureMinificationFilter::LinearMipMapLinear,
_ => {
return VisitResult::Err(VisitError::User(format!(
"Invalid minification filter {}!",
id
)))
}
}
}
visitor.leave_region()
}
}
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialOrd,
PartialEq,
Deserialize,
Serialize,
Inspect,
EnumVariantNames,
EnumString,
AsRefStr,
)]
#[repr(u32)]
pub enum TextureWrapMode {
Repeat = 0,
ClampToEdge = 1,
ClampToBorder = 2,
MirroredRepeat = 3,
MirrorClampToEdge = 4,
}
impl Default for TextureWrapMode {
fn default() -> Self {
Self::Repeat
}
}
impl Visit for TextureWrapMode {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
visitor.enter_region(name)?;
let mut id = *self as u32;
id.visit("Id", visitor)?;
if visitor.is_reading() {
*self = match id {
0 => TextureWrapMode::Repeat,
1 => TextureWrapMode::ClampToEdge,
2 => TextureWrapMode::ClampToBorder,
3 => TextureWrapMode::MirroredRepeat,
4 => TextureWrapMode::MirrorClampToEdge,
_ => {
return VisitResult::Err(VisitError::User(format!("Invalid wrap mode {}!", id)))
}
}
}
visitor.leave_region()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u32)]
pub enum TexturePixelKind {
R8 = 0,
RGB8 = 1,
RGBA8 = 2,
RG8 = 3,
R16 = 4,
RG16 = 5,
BGR8 = 6,
BGRA8 = 7,
RGB16 = 8,
RGBA16 = 9,
DXT1RGB = 10,
DXT1RGBA = 11,
DXT3RGBA = 12,
DXT5RGBA = 13,
R8RGTC = 14,
RG8RGTC = 15,
}
impl TexturePixelKind {
fn new(id: u32) -> Result<Self, String> {
match id {
0 => Ok(Self::R8),
1 => Ok(Self::RGB8),
2 => Ok(Self::RGBA8),
3 => Ok(Self::RG8),
4 => Ok(Self::R16),
5 => Ok(Self::RG16),
6 => Ok(Self::BGR8),
7 => Ok(Self::BGRA8),
8 => Ok(Self::RGB16),
9 => Ok(Self::RGBA16),
10 => Ok(Self::DXT1RGB),
11 => Ok(Self::DXT1RGBA),
12 => Ok(Self::DXT3RGBA),
13 => Ok(Self::DXT5RGBA),
14 => Ok(Self::R8RGTC),
15 => Ok(Self::RG8RGTC),
_ => Err(format!("Invalid texture kind {}!", id)),
}
}
fn id(self) -> u32 {
self as u32
}
}
#[derive(Debug, thiserror::Error)]
pub enum TextureError {
#[error("Unsupported format!")]
UnsupportedFormat,
#[error("An i/o error has occurred: {0}")]
Io(std::io::Error),
#[error("Image loading error {0}")]
Image(image::ImageError),
#[error("A file load error has occurred {0:?}")]
FileLoadError(FileLoadError),
}
impl From<FileLoadError> for TextureError {
fn from(v: FileLoadError) -> Self {
Self::FileLoadError(v)
}
}
impl From<image::ImageError> for TextureError {
fn from(v: ImageError) -> Self {
Self::Image(v)
}
}
impl From<std::io::Error> for TextureError {
fn from(v: Error) -> Self {
Self::Io(v)
}
}
fn ceil_div_4(x: u32) -> u32 {
(x + 3) / 4
}
#[derive(
Copy,
Clone,
Deserialize,
Serialize,
PartialEq,
Eq,
Debug,
Inspect,
EnumVariantNames,
EnumString,
AsRefStr,
)]
#[repr(u32)]
pub enum CompressionOptions {
NoCompression = 0,
Speed = 1,
Quality = 2,
}
impl Default for CompressionOptions {
fn default() -> Self {
Self::Quality
}
}
fn transmute_slice<T>(bytes: &[u8]) -> &'_ [T] {
unsafe {
std::slice::from_raw_parts(
bytes.as_ptr() as *const T,
bytes.len() / std::mem::size_of::<T>(),
)
}
}
fn compress_bc1<T: tbc::color::ColorRgba8>(bytes: &[u8], width: usize, height: usize) -> Vec<u8> {
tbc::encode_image_bc1_conv_u8::<T>(transmute_slice::<T>(bytes), width, height)
}
fn compress_bc3<T: tbc::color::ColorRgba8>(bytes: &[u8], width: usize, height: usize) -> Vec<u8> {
tbc::encode_image_bc3_conv_u8::<T>(transmute_slice::<T>(bytes), width, height)
}
fn compress_r8_bc4<T: tbc::color::ColorRed8>(bytes: &[u8], width: usize, height: usize) -> Vec<u8> {
tbc::encode_image_bc4_r8_conv_u8::<T>(transmute_slice::<T>(bytes), width, height)
}
fn compress_rg8_bc4<T: tbc::color::ColorRedGreen8>(
bytes: &[u8],
width: usize,
height: usize,
) -> Vec<u8> {
tbc::encode_image_bc4_rg8_conv_u8::<T>(transmute_slice::<T>(bytes), width, height)
}
fn data_hash(data: &[u8]) -> u64 {
let mut hasher = FxHasher::default();
data.hash(&mut hasher);
hasher.finish()
}
fn try_compress(
image: &DynamicImage,
w: usize,
h: usize,
compression: CompressionOptions,
) -> Option<(Vec<u8>, TexturePixelKind)> {
let bytes = image.as_bytes();
match (image, compression) {
(DynamicImage::ImageRgb8(_), CompressionOptions::Speed) => Some((
compress_bc1::<tbc::color::Rgb8>(bytes, w, h),
TexturePixelKind::DXT1RGB,
)),
(DynamicImage::ImageRgb8(_), CompressionOptions::Quality) => Some((
compress_bc3::<tbc::color::Rgb8>(bytes, w, h),
TexturePixelKind::DXT5RGBA,
)),
(DynamicImage::ImageRgba8(_), CompressionOptions::Speed) => Some((
compress_bc1::<tbc::color::Rgba8>(bytes, w, h),
TexturePixelKind::DXT1RGBA,
)),
(DynamicImage::ImageRgba8(_), CompressionOptions::Quality) => Some((
compress_bc3::<tbc::color::Rgba8>(bytes, w, h),
TexturePixelKind::DXT5RGBA,
)),
(DynamicImage::ImageLuma8(_), CompressionOptions::Speed)
| (DynamicImage::ImageLuma8(_), CompressionOptions::Quality) => Some((
compress_r8_bc4::<tbc::color::Red8>(bytes, w, h),
TexturePixelKind::R8RGTC,
)),
(DynamicImage::ImageLumaA8(_), CompressionOptions::Speed)
| (DynamicImage::ImageLumaA8(_), CompressionOptions::Quality) => Some((
compress_rg8_bc4::<tbc::color::RedGreen8>(bytes, w, h),
TexturePixelKind::RG8RGTC,
)),
_ => None,
}
}
fn bytes_in_first_mip(kind: TextureKind, pixel_kind: TexturePixelKind) -> u32 {
let pixel_count = match kind {
TextureKind::Line { length } => length,
TextureKind::Rectangle { width, height } => width * height,
TextureKind::Cube { width, height } => 6 * width * height,
TextureKind::Volume {
width,
height,
depth,
} => width * height * depth,
};
match pixel_kind {
TexturePixelKind::R8 => pixel_count,
TexturePixelKind::R16 | TexturePixelKind::RG8 => 2 * pixel_count,
TexturePixelKind::RGB8 | TexturePixelKind::BGR8 => 3 * pixel_count,
TexturePixelKind::RGBA8 | TexturePixelKind::BGRA8 | TexturePixelKind::RG16 => {
4 * pixel_count
}
TexturePixelKind::RGB16 => 6 * pixel_count,
TexturePixelKind::RGBA16 => 8 * pixel_count,
TexturePixelKind::DXT1RGB
| TexturePixelKind::DXT1RGBA
| TexturePixelKind::DXT3RGBA
| TexturePixelKind::DXT5RGBA
| TexturePixelKind::R8RGTC
| TexturePixelKind::RG8RGTC => {
let block_size = match pixel_kind {
TexturePixelKind::DXT1RGB
| TexturePixelKind::DXT1RGBA
| TexturePixelKind::R8RGTC => 8,
TexturePixelKind::DXT3RGBA
| TexturePixelKind::DXT5RGBA
| TexturePixelKind::RG8RGTC => 16,
_ => unreachable!(),
};
match kind {
TextureKind::Line { length } => ceil_div_4(length) * block_size,
TextureKind::Rectangle { width, height } => {
ceil_div_4(width) * ceil_div_4(height) * block_size
}
TextureKind::Cube { width, height } => {
6 * ceil_div_4(width) * ceil_div_4(height) * block_size
}
TextureKind::Volume {
width,
height,
depth,
} => ceil_div_4(width) * ceil_div_4(height) * ceil_div_4(depth) * block_size,
}
}
}
}
impl TextureData {
pub fn load_from_memory(
data: &[u8],
compression: CompressionOptions,
gen_mip_maps: bool,
) -> Result<Self, TextureError> {
if let Ok(dds) = ddsfile::Dds::read(&mut Cursor::new(data)) {
let d3dformat = dds
.get_d3d_format()
.ok_or(TextureError::UnsupportedFormat)?;
let mip_count = dds.get_num_mipmap_levels();
let mut bytes = dds.data;
let pixel_kind = match d3dformat {
D3DFormat::DXT1 => TexturePixelKind::DXT1RGBA,
D3DFormat::DXT3 => TexturePixelKind::DXT3RGBA,
D3DFormat::DXT5 => TexturePixelKind::DXT5RGBA,
D3DFormat::L8 | D3DFormat::A8 => TexturePixelKind::R8,
D3DFormat::L16 => TexturePixelKind::R16,
D3DFormat::R8G8B8 => TexturePixelKind::RGB8,
D3DFormat::A8L8 => TexturePixelKind::RG8,
D3DFormat::A8R8G8B8 => {
TexturePixelKind::RGBA8
}
D3DFormat::G16R16 => {
assert_eq!(bytes.len() % 4, 0);
for chunk in bytes.chunks_exact_mut(4) {
let gh = chunk[0];
let gl = chunk[1];
let rh = chunk[2];
let rl = chunk[3];
chunk[0] = rh;
chunk[1] = rl;
chunk[2] = gh;
chunk[3] = gl;
}
TexturePixelKind::RG16
}
_ => return Err(TextureError::UnsupportedFormat),
};
Ok(Self {
pixel_kind,
data_hash: data_hash(&bytes),
minification_filter: TextureMinificationFilter::LinearMipMapLinear,
magnification_filter: TextureMagnificationFilter::Linear,
s_wrap_mode: TextureWrapMode::Repeat,
t_wrap_mode: TextureWrapMode::Repeat,
mip_count,
bytes: bytes.into(),
kind: if dds.header.caps2 & Caps2::CUBEMAP == Caps2::CUBEMAP {
TextureKind::Cube {
width: dds.header.width,
height: dds.header.height,
}
} else if dds.header.caps2 & Caps2::VOLUME == Caps2::VOLUME {
TextureKind::Volume {
width: dds.header.width,
height: dds.header.height,
depth: dds.header.depth.unwrap(),
}
} else {
TextureKind::Rectangle {
width: dds.header.width,
height: dds.header.height,
}
},
..Default::default()
})
} else {
let dyn_img = image::load_from_memory(data)
.or_else(|_| image::load_from_memory_with_format(data, ImageFormat::Tga))?;
let width = dyn_img.width();
let height = dyn_img.height();
let mut pixel_kind = match dyn_img {
DynamicImage::ImageLuma8(_) => TexturePixelKind::R8,
DynamicImage::ImageLumaA8(_) => TexturePixelKind::RG8,
DynamicImage::ImageRgb8(_) => TexturePixelKind::RGB8,
DynamicImage::ImageRgba8(_) => TexturePixelKind::RGBA8,
DynamicImage::ImageBgr8(_) => TexturePixelKind::BGR8,
DynamicImage::ImageBgra8(_) => TexturePixelKind::BGRA8,
DynamicImage::ImageLuma16(_) => TexturePixelKind::R16,
DynamicImage::ImageLumaA16(_) => TexturePixelKind::RG16,
DynamicImage::ImageRgb16(_) => TexturePixelKind::RGB16,
DynamicImage::ImageRgba16(_) => TexturePixelKind::RGBA16,
};
let mut mip_count = 0;
let mut bytes = Vec::new();
if gen_mip_maps {
let mut level_width = width;
let mut level_height = height;
let mut current_level = dyn_img;
while level_width != 0 && level_height != 0 {
if mip_count != 0 {
current_level = current_level.resize_exact(
level_width,
level_height,
FilterType::Lanczos3,
);
}
mip_count += 1;
if compression == CompressionOptions::NoCompression {
bytes.extend_from_slice(current_level.as_bytes())
} else if let Some((compressed_data, new_pixel_kind)) = try_compress(
¤t_level,
level_width as usize,
level_height as usize,
compression,
) {
pixel_kind = new_pixel_kind;
bytes.extend_from_slice(&compressed_data);
} else {
bytes.extend_from_slice(current_level.as_bytes())
}
level_width = level_width.checked_shr(1).unwrap_or_default();
level_height = level_height.checked_shr(1).unwrap_or_default();
}
} else {
mip_count = 1;
if compression == CompressionOptions::NoCompression {
bytes.extend_from_slice(dyn_img.as_bytes());
} else if let Some((compressed_data, new_pixel_kind)) =
try_compress(&dyn_img, width as usize, height as usize, compression)
{
pixel_kind = new_pixel_kind;
bytes.extend_from_slice(&compressed_data);
} else {
bytes.extend_from_slice(dyn_img.as_bytes())
}
}
Ok(Self {
pixel_kind,
kind: TextureKind::Rectangle { width, height },
data_hash: data_hash(&bytes),
bytes: bytes.into(),
mip_count,
..Default::default()
})
}
}
pub(in crate) async fn load_from_file<P: AsRef<Path>>(
path: P,
compression: CompressionOptions,
gen_mip_maps: bool,
) -> Result<Self, TextureError> {
let data = io::load_file(path.as_ref()).await?;
let mut texture = Self::load_from_memory(&data, compression, gen_mip_maps)?;
texture.path = path.as_ref().to_path_buf();
Ok(texture)
}
pub fn from_bytes(
kind: TextureKind,
pixel_kind: TexturePixelKind,
bytes: Vec<u8>,
serialize_content: bool,
) -> Option<Self> {
if bytes_in_first_mip(kind, pixel_kind) != bytes.len() as u32 {
None
} else {
Some(Self {
path: Default::default(),
kind,
data_hash: data_hash(&bytes),
bytes: bytes.into(),
pixel_kind,
serialize_content,
..Default::default()
})
}
}
pub fn set_minification_filter(&mut self, filter: TextureMinificationFilter) {
self.minification_filter = filter;
}
pub fn minification_filter(&self) -> TextureMinificationFilter {
self.minification_filter
}
pub fn set_magnification_filter(&mut self, filter: TextureMagnificationFilter) {
self.magnification_filter = filter;
}
pub fn magnification_filter(&self) -> TextureMagnificationFilter {
self.magnification_filter
}
pub fn set_s_wrap_mode(&mut self, s_wrap_mode: TextureWrapMode) {
self.s_wrap_mode = s_wrap_mode;
}
pub fn s_wrap_mode(&self) -> TextureWrapMode {
self.s_wrap_mode
}
pub fn set_t_wrap_mode(&mut self, t_wrap_mode: TextureWrapMode) {
self.t_wrap_mode = t_wrap_mode;
}
pub fn t_wrap_mode(&self) -> TextureWrapMode {
self.t_wrap_mode
}
pub fn mip_count(&self) -> u32 {
self.mip_count
}
pub fn kind(&self) -> TextureKind {
self.kind
}
pub fn data_hash(&self) -> u64 {
self.data_hash
}
pub fn pixel_kind(&self) -> TexturePixelKind {
self.pixel_kind
}
pub fn data(&self) -> &[u8] {
&self.bytes
}
pub fn first_mip_level_data(&self) -> &[u8] {
&self.bytes[0..bytes_in_first_mip(self.kind, self.pixel_kind) as usize]
}
pub fn is_procedural(&self) -> bool {
self.serialize_content
}
pub fn is_render_target(&self) -> bool {
self.is_render_target
}
pub fn set_anisotropy_level(&mut self, anisotropy: f32) {
self.anisotropy = anisotropy.max(1.0);
}
pub fn anisotropy_level(&self) -> f32 {
self.anisotropy
}
pub fn set_path<P: AsRef<Path>>(&mut self, path: P) {
self.path = path.as_ref().to_owned();
}
pub fn save(&self) -> Result<(), TextureError> {
let color_type = match self.pixel_kind {
TexturePixelKind::R8 => ColorType::L8,
TexturePixelKind::RGB8 => ColorType::Rgb8,
TexturePixelKind::RGBA8 => ColorType::Rgba8,
TexturePixelKind::RG8 => ColorType::La8,
TexturePixelKind::R16 => ColorType::L16,
TexturePixelKind::RG16 => ColorType::La16,
TexturePixelKind::BGR8 => ColorType::Bgr8,
TexturePixelKind::BGRA8 => ColorType::Bgra8,
TexturePixelKind::RGB16 => ColorType::Rgb16,
TexturePixelKind::RGBA16 => ColorType::Rgba16,
TexturePixelKind::DXT1RGB
| TexturePixelKind::DXT1RGBA
| TexturePixelKind::DXT3RGBA
| TexturePixelKind::DXT5RGBA
| TexturePixelKind::R8RGTC
| TexturePixelKind::RG8RGTC => return Err(TextureError::UnsupportedFormat),
};
if let TextureKind::Rectangle { width, height } = self.kind {
Ok(image::save_buffer(
&self.path,
self.bytes.as_ref(),
width,
height,
color_type,
)?)
} else {
Err(TextureError::UnsupportedFormat)
}
}
pub fn modify(&mut self) -> TextureDataRefMut<'_> {
TextureDataRefMut { texture: self }
}
}
pub struct TextureDataRefMut<'a> {
texture: &'a mut TextureData,
}
impl<'a> Drop for TextureDataRefMut<'a> {
fn drop(&mut self) {
self.texture.data_hash = data_hash(&self.texture.bytes);
}
}
impl<'a> Deref for TextureDataRefMut<'a> {
type Target = TextureData;
fn deref(&self) -> &Self::Target {
self.texture
}
}
impl<'a> DerefMut for TextureDataRefMut<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.texture
}
}
impl<'a> TextureDataRefMut<'a> {
pub fn data_mut(&mut self) -> &mut [u8] {
&mut self.texture.bytes
}
}