extern crate futures;
#[cfg(not(target_arch="wasm32"))]
extern crate image;
use error::QuicksilverError;
use ffi::gl;
use futures::{Async, Future, Poll};
use geom::{Rectangle, Vector};
use std::{
error::Error,
fmt,
io::Error as IOError,
ops::Drop,
os::raw::c_void,
path::Path,
rc::Rc
};
#[cfg(not(target_arch="wasm32"))]
use std::path::PathBuf;
#[derive(Debug, Eq, PartialEq, Hash)]
pub enum PixelFormat {
RGB = gl::RGB as isize,
RGBA = gl::RGBA as isize,
BGR = gl::BGR as isize,
BGRA = gl::BGRA as isize,
}
#[derive(Debug)]
struct ImageData {
id: u32,
width: u32,
height: u32,
}
impl Drop for ImageData {
fn drop(&mut self) {
unsafe {
gl::DeleteTexture(self.id);
}
}
}
#[derive(Clone, Debug)]
pub struct Image {
source: Rc<ImageData>,
region: Rectangle,
}
impl Image {
fn new(data: ImageData) -> Image {
let region = Rectangle::new_sized(data.width, data.height);
Image {
source: Rc::new(data),
region
}
}
pub fn load<P: AsRef<Path>>(path: P) -> ImageLoader {
Image::load_impl(path)
}
#[cfg(target_arch="wasm32")]
fn load_impl<P: AsRef<Path>>(path: P) -> ImageLoader {
use std::ffi::CString;
use ffi::wasm;
ImageLoader {
id: unsafe { wasm::load_image(CString::new(path.as_ref().to_str().unwrap()).unwrap().into_raw()) }
}
}
#[cfg(not(target_arch="wasm32"))]
fn load_impl<P: AsRef<Path>>(path: P) -> ImageLoader {
ImageLoader {
path: PathBuf::from(path.as_ref())
}
}
fn from_ptr(data: *const c_void, width: u32, height: u32, format: PixelFormat) -> Image {
unsafe {
let id = gl::GenTexture();
gl::BindTexture(gl::TEXTURE_2D, id);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGBA as i32, width as i32,
height as i32, 0, format as u32, gl::UNSIGNED_BYTE, data);
gl::GenerateMipmap(gl::TEXTURE_2D);
Image::new(ImageData { id, width, height })
}
}
pub(crate) fn new_null(width: u32, height: u32, format: PixelFormat) -> Image {
use std::ptr::null;
Image::from_ptr(null(), width, height, format)
}
pub fn from_raw(data: &[u8], width: u32, height: u32, format: PixelFormat) -> Image {
Image::from_ptr(data.as_ptr() as *const c_void, width, height, format)
}
pub(crate) fn get_id(&self) -> u32 {
self.source.id
}
pub(crate) fn source_width(&self) -> u32 {
self.source.width
}
pub(crate) fn source_height(&self) -> u32 {
self.source.height
}
pub(crate) fn source_size(&self) -> Vector {
Vector::new(self.source_width(), self.source_height())
}
pub fn area(&self) -> Rectangle {
self.region
}
pub fn subimage(&self, rect: Rectangle) -> Image {
Image {
source: self.source.clone(),
region: Rectangle::new(
self.region.x + rect.x,
self.region.y + rect.y,
rect.width,
rect.height,
),
}
}
}
pub struct ImageLoader {
#[cfg(not(target_arch="wasm32"))]
path: PathBuf,
#[cfg(target_arch="wasm32")]
id: u32
}
impl Future for ImageLoader {
type Item = Image;
type Error = QuicksilverError;
#[cfg(not(target_arch="wasm32"))]
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let img = image::open(&self.path)?.to_rgba();
let width = img.width();
let height = img.height();
Ok(Async::Ready(Image::from_raw(img.into_raw().as_slice(), width, height, PixelFormat::RGBA)))
}
#[cfg(target_arch="wasm32")]
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
use ffi::wasm;
Ok(match wasm::asset_status(self.id)? {
true => Async::Ready(Image::new(ImageData {
id: unsafe { wasm::get_image_id(self.id) },
width: unsafe { wasm::get_image_width(self.id) },
height: unsafe { wasm::get_image_height(self.id) }
})),
false => Async::NotReady,
})
}
}
#[derive(Debug)]
pub enum ImageError {
FormatError(String),
DimensionError,
UnsupportedError(String),
UnsupportedColor,
NotEnoughData,
IOError(IOError),
ImageEnd,
}
#[doc(hidden)]
impl From<IOError> for ImageError {
fn from(err: IOError) -> ImageError {
ImageError::IOError(err)
}
}
#[doc(hidden)]
#[cfg(not(target_arch="wasm32"))]
impl From<image::ImageError> for ImageError {
fn from(img: image::ImageError) -> ImageError {
match img {
image::ImageError::FormatError(string) => ImageError::FormatError(string),
image::ImageError::DimensionError => ImageError::DimensionError,
image::ImageError::UnsupportedError(string) => ImageError::UnsupportedError(string),
image::ImageError::UnsupportedColor(_) => ImageError::UnsupportedColor,
image::ImageError::NotEnoughData => ImageError::NotEnoughData,
image::ImageError::IoError(err) => ImageError::IOError(err),
image::ImageError::ImageEnd => ImageError::ImageEnd
}
}
}
impl fmt::Display for ImageError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl Error for ImageError {
fn description(&self) -> &str {
match self {
&ImageError::FormatError(ref string) => string,
&ImageError::DimensionError => "Invalid dimensions",
&ImageError::UnsupportedError(ref string) => string,
&ImageError::UnsupportedColor => "Unsupported colorspace",
&ImageError::NotEnoughData => "Not enough image data",
&ImageError::IOError(ref err) => err.description(),
&ImageError::ImageEnd => "Image data ended unexpectedly"
}
}
fn cause(&self) -> Option<&Error> {
match self {
&ImageError::IOError(ref err) => Some(err),
_ => None
}
}
}