use std::io::Read;
use std::path;
use ::image;
use gfx;
use crate::context::{Context, DebugId};
use crate::error::GameError;
use crate::error::GameResult;
use crate::filesystem;
use crate::graphics;
use crate::graphics::shader::*;
use crate::graphics::*;
#[derive(Clone, PartialEq)]
pub struct ImageGeneric<B>
where
B: BackendSpec,
{
pub(crate) texture: gfx::handle::RawShaderResourceView<B::Resources>,
pub(crate) texture_handle: gfx::handle::RawTexture<B::Resources>,
pub(crate) sampler_info: gfx::texture::SamplerInfo,
pub(crate) blend_mode: Option<BlendMode>,
pub(crate) width: u16,
pub(crate) height: u16,
pub(crate) debug_id: DebugId,
}
impl<B> ImageGeneric<B>
where
B: BackendSpec,
{
pub(crate) fn make_raw(
factory: &mut <B as BackendSpec>::Factory,
sampler_info: &texture::SamplerInfo,
width: u16,
height: u16,
rgba: &[u8],
color_format: gfx::format::Format,
debug_id: DebugId,
) -> GameResult<Self> {
if width == 0 || height == 0 {
let msg = format!(
"Tried to create a texture of size {}x{}, each dimension must
be >0",
width, height
);
return Err(GameError::ResourceLoadError(msg));
}
let uwidth = width as usize;
let uheight = height as usize;
let expected_bytes = uwidth
.checked_mul(uheight)
.and_then(|size| size.checked_mul(4))
.ok_or_else(|| {
let msg = format!(
"Integer overflow in Image::make_raw, image size: {} {}",
uwidth, uheight
);
GameError::ResourceLoadError(msg)
})?;
if expected_bytes != rgba.len() {
let msg = format!(
"Tried to create a texture of size {}x{}, but gave {} bytes of data (expected {})",
width,
height,
rgba.len(),
expected_bytes
);
return Err(GameError::ResourceLoadError(msg));
}
let kind = gfx::texture::Kind::D2(width, height, gfx::texture::AaMode::Single);
use gfx::memory::Bind;
let gfx::format::Format(surface_format, channel_type) = color_format;
let texinfo = gfx::texture::Info {
kind,
levels: 1,
format: surface_format,
bind: Bind::SHADER_RESOURCE
| Bind::RENDER_TARGET
| Bind::TRANSFER_SRC
| Bind::TRANSFER_DST,
usage: gfx::memory::Usage::Dynamic,
};
let raw_tex = factory.create_texture_raw(
texinfo,
Some(channel_type),
Some((&[rgba], gfx::texture::Mipmap::Provided)),
)?;
let resource_desc = gfx::texture::ResourceDesc {
channel: channel_type,
layer: None,
min: 0,
max: raw_tex.get_info().levels - 1,
swizzle: gfx::format::Swizzle::new(),
};
let raw_view = factory.view_texture_as_shader_resource_raw(&raw_tex, resource_desc)?;
Ok(Self {
texture: raw_view,
texture_handle: raw_tex,
sampler_info: *sampler_info,
blend_mode: None,
width,
height,
debug_id,
})
}
}
pub type Image = ImageGeneric<GlBackendSpec>;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ImageFormat {
Png,
}
impl Image {
pub fn new<P: AsRef<path::Path>>(context: &mut Context, path: P) -> GameResult<Self> {
let img = {
let mut buf = Vec::new();
let mut reader = context.filesystem.open(path)?;
let _ = reader.read_to_end(&mut buf)?;
image::load_from_memory(&buf)?.to_rgba()
};
let (width, height) = img.dimensions();
Self::from_rgba8(context, width as u16, height as u16, &img)
}
pub fn from_rgba8(
context: &mut Context,
width: u16,
height: u16,
rgba: &[u8],
) -> GameResult<Self> {
let debug_id = DebugId::get(context);
let color_format = context.gfx_context.color_format();
Self::make_raw(
&mut *context.gfx_context.factory,
&context.gfx_context.default_sampler_info,
width,
height,
rgba,
color_format,
debug_id,
)
}
pub fn to_rgba8(&self, ctx: &mut Context) -> GameResult<Vec<u8>> {
use gfx::memory::Typed;
use gfx::traits::FactoryExt;
let gfx = &mut ctx.gfx_context;
let w = self.width;
let h = self.height;
let dl_buffer = gfx
.factory
.create_download_buffer::<[u8; 4]>(w as usize * h as usize)?;
let factory = &mut *gfx.factory;
let mut local_encoder = GlBackendSpec::encoder(factory);
local_encoder.copy_texture_to_buffer_raw(
&self.texture_handle,
None,
gfx::texture::RawImageInfo {
xoffset: 0,
yoffset: 0,
zoffset: 0,
width: w as u16,
height: h as u16,
depth: 0,
format: gfx.color_format(),
mipmap: 0,
},
dl_buffer.raw(),
0,
)?;
local_encoder.flush(&mut *gfx.device);
let reader = gfx.factory.read_mapping(&dl_buffer)?;
let mut data = Vec::with_capacity(self.width as usize * self.height as usize * 4);
data.extend(reader.into_iter().flatten());
Ok(data)
}
pub fn encode<P: AsRef<path::Path>>(
&self,
ctx: &mut Context,
format: ImageFormat,
path: P,
) -> GameResult {
use std::io;
let data = self.to_rgba8(ctx)?;
let f = filesystem::create(ctx, path)?;
let writer = &mut io::BufWriter::new(f);
let color_format = image::ColorType::RGBA(8);
match format {
ImageFormat::Png => image::png::PNGEncoder::new(writer)
.encode(
&data,
u32::from(self.width),
u32::from(self.height),
color_format,
)
.map_err(Into::into),
}
}
pub fn solid(context: &mut Context, size: u16, color: Color) -> GameResult<Self> {
let (r, g, b, a) = color.into();
let pixel_array: [u8; 4] = [r, g, b, a];
let size_squared = size as usize * size as usize;
let mut buffer = Vec::with_capacity(size_squared);
for _i in 0..size_squared {
buffer.extend(&pixel_array[..]);
}
Image::from_rgba8(context, size, size, &buffer)
}
pub fn width(&self) -> u16 {
self.width
}
pub fn height(&self) -> u16 {
self.height
}
pub fn filter(&self) -> FilterMode {
self.sampler_info.filter.into()
}
pub fn set_filter(&mut self, mode: FilterMode) {
self.sampler_info.filter = mode.into();
}
pub fn dimensions(&self) -> Rect {
Rect::new(0.0, 0.0, f32::from(self.width()), f32::from(self.height()))
}
pub fn wrap(&self) -> (WrapMode, WrapMode) {
(self.sampler_info.wrap_mode.0, self.sampler_info.wrap_mode.1)
}
pub fn set_wrap(&mut self, wrap_x: WrapMode, wrap_y: WrapMode) {
self.sampler_info.wrap_mode.0 = wrap_x;
self.sampler_info.wrap_mode.1 = wrap_y;
}
}
impl fmt::Debug for Image {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"<Image: {}x{}, {:p}, texture address {:p}, sampler: {:?}>",
self.width(),
self.height(),
self,
&self.texture,
&self.sampler_info
)
}
}
impl Drawable for Image {
fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult {
self.debug_id.assert(ctx);
let gfx = &mut ctx.gfx_context;
let src_width = param.src.w;
let src_height = param.src.h;
let real_scale = nalgebra::Vector2::new(
param.scale.x * src_width * f32::from(self.width),
param.scale.y * src_height * f32::from(self.height),
);
let mut new_param = param;
new_param.scale = real_scale.into();
gfx.update_instance_properties(new_param.into())?;
let sampler = gfx
.samplers
.get_or_insert(self.sampler_info, gfx.factory.as_mut());
gfx.data.vbuf = gfx.quad_vertex_buffer.clone();
let typed_thingy = gfx
.backend_spec
.raw_to_typed_shader_resource(self.texture.clone());
gfx.data.tex = (typed_thingy, sampler);
let previous_mode: Option<BlendMode> = if let Some(mode) = self.blend_mode {
let current_mode = gfx.blend_mode();
if current_mode != mode {
gfx.set_blend_mode(mode)?;
Some(current_mode)
} else {
None
}
} else {
None
};
gfx.draw(None)?;
if let Some(mode) = previous_mode {
gfx.set_blend_mode(mode)?;
}
Ok(())
}
fn dimensions(&self, _: &mut Context) -> Option<graphics::Rect> {
Some(self.dimensions())
}
fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
self.blend_mode = mode;
}
fn blend_mode(&self) -> Option<BlendMode> {
self.blend_mode
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ContextBuilder;
#[test]
fn test_invalid_image_size() {
let (ctx, _) = &mut ContextBuilder::new("unittest", "unittest").build().unwrap();
let _i = assert!(Image::from_rgba8(ctx, 0, 0, &vec![]).is_err());
let _i = assert!(Image::from_rgba8(ctx, 3432, 432, &vec![]).is_err());
let _i = Image::from_rgba8(ctx, 2, 2, &vec![99; 16]).unwrap();
}
}