nsys-gl-utils 0.11.3

OpenGL and graphics utilities
//! Texture related utilities.

use std;
use glium;
use image;
use log;

/// An RGBA 16x16 square of pixels with white color values with an opaque cross
/// of two pixels thickness
pub const CROSSHAIR_PNG_FILE_BYTES                        : &'static [u8; 101] =
  include_bytes!("../crosshair.png");
/// For use with `BLEND_FUNC_INVERT_COLOR`-- transparent pixels have *black*
/// color values instead of white but the opaque portion is still white
pub const CROSSHAIR_INVERSE_PNG_FILE_BYTES                : &'static [u8; 95]  =
  include_bytes!("../crosshair-inverse.png");
pub const TILESET_EASCII_ACORN_8X8_PNG_FILE_BYTES         : &'static [u8; 2538] =
  include_bytes!("../tileset_eascii_acorn-bbc-micro_8x8.png");
pub const TILESET_EASCII_ACORN_8X8_INVERSE_PNG_FILE_BYTES : &'static [u8; 2526] =
  include_bytes!("../tileset_eascii_acorn-bbc-micro_8x8-inverse.png");
pub const TILESET_EASCII_ACORN_16X16_PNG_FILE_BYTES       : &'static [u8; 3427] =
  include_bytes!("../tileset_eascii_acorn-bbc-micro_16x16.png");
pub const TILESET_EASCII_ACORN_16X16_INVERSE_PNG_FILE_BYTES : &'static [u8; 3427] =
  include_bytes!("../tileset_eascii_acorn-bbc-micro_16x16-inverse.png");
pub const POINTER_HAND_PNG_FILE_BYTES_OFFSET : (&'static [u8; 183], [i16; 2]) =
  (include_bytes!("../pointer-hand.png"), [3, -10]);

#[derive(Debug)]
pub enum LoadError {
  IoError              (std::io::Error),
  ImageError           (image::ImageError),
  TextureCreationError (glium::texture::TextureCreationError)
}

/// Load a 2D texture from the given bytes with the given format and mipmaps.
pub fn texture2d_with_mipmaps_from_bytes (
  glium_facade : &dyn glium::backend::Facade,
  bytes        : &[u8],
  image_format : image::ImageFormat,
  mipmaps      : glium::texture::MipmapsOption
) -> Result <glium::Texture2d, LoadError> {
  let img = image::load_from_memory_with_format (bytes, image_format)?
    .to_rgba8();
  let img_dimensions = img.dimensions();
  log::debug!(dimensions:?=img_dimensions; "texture load bytes");
  let raw_image_2d = glium::texture::RawImage2d::from_raw_rgba_reversed (
    img.into_raw().as_slice(),
    img_dimensions);

  glium::Texture2d::with_mipmaps (glium_facade, raw_image_2d, mipmaps)
    .map_err (Into::into)
}

/// Load a 2D texture from the given path with the given format and mipmaps.
pub fn texture2d_with_mipmaps_from_file (
  glium_facade : &dyn glium::backend::Facade,
  filepath     : &'static str,
  image_format : image::ImageFormat,
  mipmaps      : glium::texture::MipmapsOption
) -> Result <glium::Texture2d, LoadError> {
  use std::io::Read;
  log::debug!(filepath:?; "texture load file");
  let mut file  = std::fs::File::open (filepath)?;
  let mut bytes = Vec::new();
  let _         = file.read_to_end (&mut bytes)?;
  texture2d_with_mipmaps_from_bytes (
    glium_facade, bytes.as_slice(), image_format, mipmaps)
}

/// Load a 2D texture array from the given vector of byte slices for each
/// individual texture, with the given format and mipmaps.
pub fn texture2darray_with_mipmaps_from_bytes (
  glium_facade : &dyn glium::backend::Facade,
  bytes_slices : &[&[u8]],
  image_format : image::ImageFormat,
  mipmaps      : glium::texture::MipmapsOption
) -> Result <glium::texture::Texture2dArray, LoadError> {
  let raw_images = {
    let mut v = Vec::with_capacity (bytes_slices.len());
    for bytes in bytes_slices {
      let img = image::load_from_memory_with_format (bytes, image_format)?
        .to_rgba8();
      let img_dimensions = img.dimensions();
      log::debug!(dimensions:?=img_dimensions; "texture array load bytes");
      let raw_image_2d = glium::texture::RawImage2d::from_raw_rgba_reversed (
        img.into_raw().as_slice(),
        img_dimensions);
      v.push (raw_image_2d);
    }
    v
  };

  glium::texture::Texture2dArray::with_mipmaps (
    glium_facade, raw_images, mipmaps
  ).map_err (Into::into)
}

/// Load a 2D texture array from the given paths with the given format and
/// mipmaps.
pub fn texture2darray_with_mipmaps_from_files (
  glium_facade : &dyn glium::backend::Facade,
  filepaths    : &[&'static str],
  image_format : image::ImageFormat,
  mipmaps      : glium::texture::MipmapsOption
) -> Result <glium::texture::Texture2dArray, LoadError> {
  use std::io::Read;

  if filepaths.is_empty() {
    return Err (std::io::Error::new (std::io::ErrorKind::InvalidInput,
      "no input paths provided").into())
  }

  let bytes = {
    let mut v = Vec::with_capacity (filepaths.len());
    v.resize_with (filepaths.len(), Default::default);
    for (i, filepath) in filepaths.iter().enumerate() {
      log::debug!(filepath:?; "texture load file");
      let mut file = std::fs::File::open (filepath)?;
      let _        = file.read_to_end (&mut v[i])?;
    }
    v
  };

  let bytes_vec = {
    let mut v = Vec::with_capacity (filepaths.len());
    for bytes in bytes.iter() {
      v.push (bytes.as_slice());
    }
    v
  };

  let texture2darray = texture2darray_with_mipmaps_from_bytes (
    glium_facade, &bytes_vec, image_format, mipmaps)?;
  // bytes must live until here
  Ok (texture2darray)
}

//
//  impls
//
impl std::fmt::Display for LoadError {
  fn fmt (&self, fmt : &mut std::fmt::Formatter) -> std::fmt::Result {
    match *self {
      LoadError::IoError              (ref err) =>
         write!(fmt, "I/O error: {}", err),
      LoadError::ImageError           (ref err) => err.fmt (fmt),
      LoadError::TextureCreationError (ref err) => err.fmt (fmt)
    }
  }
}

impl std::error::Error for LoadError {
  fn source (&self) -> Option <&(dyn std::error::Error + 'static)> {
    match *self {
      LoadError::IoError              (ref err) => err.source(),
      LoadError::ImageError           (ref err) => err.source(),
      LoadError::TextureCreationError (ref err) => err.source()
    }
  }
}

impl From <std::io::Error> for LoadError {
  fn from (err : std::io::Error) -> Self {
    LoadError::IoError (err)
  }
}

impl From <image::ImageError> for LoadError {
  fn from (err : image::ImageError) -> Self {
    LoadError::ImageError (err)
  }
}

impl From <glium::texture::TextureCreationError> for LoadError {
  fn from (err : glium::texture::TextureCreationError) -> Self {
    LoadError::TextureCreationError (err)
  }
}