use std;
use std::fs;
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
use std::sync::{Arc, Mutex};
use std::thread;
use gelatin::glium;
use gelatin::image::{self, gif::GifDecoder, png::PngDecoder, AnimationDecoder, ImageFormat};
use glium::texture::{MipmapsOption, RawImage2d, SrgbTexture2d};
pub mod errors {
use gelatin::glium::texture;
use gelatin::image;
use std::io;
error_chain! {
foreign_links {
Io(io::Error) #[doc = "Error during IO"];
TextureCreationError(texture::TextureCreationError);
ImageLoadError(image::ImageError);
ExifError(exif::Error);
}
}
}
use self::errors::*;
pub static PRIORITY_REQUEST_ID: AtomicU32 = AtomicU32::new(0); pub const NON_EXISTENT_REQUEST_ID: u32 = std::u32::MAX;
pub enum ImgFormat {
Image(ImageFormat),
#[cfg(feature = "avif")]
Avif,
}
pub fn detect_format(path: &Path) -> Result<ImgFormat> {
let mut file = fs::File::open(path)?;
let mut file_start_bytes = [0; 512];
if file.read_exact(&mut file_start_bytes).is_ok() {
#[cfg(feature = "avif")]
{
if libavif_image::is_avif(&file_start_bytes) {
return Ok(ImgFormat::Avif);
}
}
if let Ok(format) = image::guess_format(&file_start_bytes) {
return Ok(ImgFormat::Image(format));
}
}
Ok(ImgFormat::Image(ImageFormat::from_path(path)?))
}
fn detect_orientation(path: &Path) -> Result<f32> {
let file = std::fs::File::open(path)?;
let mut bufreader = std::io::BufReader::new(&file);
let exifreader = exif::Reader::new();
let exif = exifreader.read_from_container(&mut bufreader)?;
if let Some(orientation) = exif.get_field(exif::Tag::Orientation, exif::In::PRIMARY) {
if let exif::Value::Short(ref shorts) = orientation.value {
if let Some(&exif_orientation) = shorts.get(0) {
use std::f32::consts::PI;
match exif_orientation {
1 => Ok(0.0),
2 => {
eprintln!("Image is flipped according to the exif data. This is not yet supported.");
Ok(0.0)
}
3 => Ok(PI),
4 => {
eprintln!("Image is flipped according to the exif data. This is not yet supported.");
Ok(PI)
}
5 => {
eprintln!("Image is flipped according to the exif data. This is not yet supported.");
Ok(PI * 0.5)
}
6 => Ok(PI * -0.5),
7 => {
eprintln!("Image is flipped according to the exif data. This is not yet supported.");
Ok(PI * -0.5)
}
8 => Ok(PI * 0.5),
_ => unreachable!(),
}
} else {
Ok(0.0)
}
} else {
Err("EXIF orientation was expected to be of type 'short' but it wasn't".into())
}
} else {
Ok(0.0)
}
}
pub fn load_image(path: &Path, image_format: ImageFormat) -> Result<image::RgbaImage> {
let reader = BufReader::new(fs::File::open(path)?);
Ok(image::load(reader, image_format)?.to_rgba())
}
pub fn load_gif(path: &Path, req_id: u32) -> Result<impl Iterator<Item = Result<LoadResult>>> {
let file = fs::File::open(path)?;
let decoder = GifDecoder::new(file)?;
load_animation(req_id, decoder)
}
fn load_animation(
req_id: u32,
decoder: impl AnimationDecoder<'static>,
) -> Result<impl Iterator<Item = Result<LoadResult>>> {
let frames = decoder.into_frames();
Ok(frames.map(move |frame| {
Ok(frame.map(|frame| {
let (numerator_ms, denom_ms) = frame.delay().numer_denom_ms();
let numerator_nano = numerator_ms as u64 * 1_000_000;
let denom_nano = denom_ms as u64;
let delay_nano = numerator_nano / denom_nano;
let image = frame.into_buffer();
LoadResult::Frame { req_id, image, delay_nano, angle: 0.0 }
})?)
}))
}
pub fn texture_from_image(
display: &glium::Display,
image: image::RgbaImage,
) -> Result<SrgbTexture2d> {
let dimensions = image.dimensions();
let data = image.into_raw();
let raw_image = RawImage2d::from_raw_rgba(data, dimensions);
let x_pow = (31 as u32) - dimensions.0.leading_zeros();
let y_pow = (31 as u32) - dimensions.1.leading_zeros();
let max_mipmap_levels = x_pow.min(y_pow).min(4);
let mipmaps = if max_mipmap_levels == 1 {
MipmapsOption::NoMipmap
} else {
MipmapsOption::AutoGeneratedMipmapsMax(max_mipmap_levels)
};
Ok(SrgbTexture2d::with_mipmaps(display, raw_image, mipmaps)?)
}
pub fn is_file_supported(filename: &Path) -> bool {
if let Some(ext) = filename.extension() {
if let Some(ext) = ext.to_str() {
let ext = ext.to_lowercase();
match ext.as_str() {
"jpg" | "jpeg" | "png" | "apng" | "gif" | "webp" | "tif" | "tiff" | "tga"
| "bmp" | "ico" | "hdr" | "pbm" | "pam" | "ppm" | "pgm" => {
return true;
}
#[cfg(feature = "avif")]
"avif" => return true,
_ => (),
}
}
}
false
}
#[derive(Debug, Clone)]
pub struct LoadRequest {
pub req_id: u32,
pub path: PathBuf,
}
pub enum LoadResult {
Start {
req_id: u32,
metadata: fs::Metadata,
},
Frame {
req_id: u32,
image: image::RgbaImage,
delay_nano: u64,
angle: f32,
},
Done {
req_id: u32,
},
Failed {
req_id: u32,
},
}
impl LoadResult {
pub fn req_id(&self) -> u32 {
match self {
LoadResult::Start { req_id, .. } => *req_id,
LoadResult::Frame { req_id, .. } => *req_id,
LoadResult::Done { req_id, .. } => *req_id,
LoadResult::Failed { req_id, .. } => *req_id,
}
}
}
pub struct ImageLoader {
running: Arc<AtomicBool>,
join_handles: Option<Vec<thread::JoinHandle<()>>>,
image_rx: Receiver<LoadResult>,
path_tx: Sender<LoadRequest>,
}
impl ImageLoader {
pub fn new(threads: u32) -> ImageLoader {
let running = Arc::new(AtomicBool::from(true));
let (load_request_tx, load_request_rx) = channel();
let load_request_rx = Arc::new(Mutex::new(load_request_rx));
let (loaded_img_tx, loaded_img_rx) = channel();
let mut join_handles = Vec::new();
for _ in 0..threads {
let running = running.clone();
let request_recv = load_request_rx.clone();
let request_send = load_request_tx.clone();
let img_sender = loaded_img_tx.clone();
join_handles.push(thread::spawn(move || {
Self::thread_loop(running, request_recv, request_send, img_sender);
}));
}
ImageLoader {
running,
join_handles: Some(join_handles),
image_rx: loaded_img_rx,
path_tx: load_request_tx,
}
}
fn thread_loop(
running: Arc<AtomicBool>,
request_recv: Arc<Mutex<Receiver<LoadRequest>>>,
request_send: Sender<LoadRequest>,
img_sender: Sender<LoadResult>,
) {
while running.load(Ordering::Acquire) {
let request;
{
let load_request = request_recv.lock().unwrap();
let priority = PRIORITY_REQUEST_ID.load(Ordering::SeqCst);
request = load_request.recv().unwrap();
let focus_test_passed =
priority == request.req_id || priority == NON_EXISTENT_REQUEST_ID;
if !focus_test_passed {
request_send.send(request).unwrap();
continue;
}
};
Self::load_and_send(&img_sender, request);
}
}
pub fn try_recv_prefetched(&mut self) -> std::result::Result<LoadResult, TryRecvError> {
self.image_rx.try_recv()
}
pub fn send_load_request(&mut self, request: LoadRequest) {
self.path_tx.send(request).unwrap();
}
fn load_and_send(img_sender: &Sender<LoadResult>, request: LoadRequest) {
fn try_load_and_send(img_sender: &Sender<LoadResult>, request: &LoadRequest) -> Result<()> {
let metadata = fs::metadata(&request.path)?;
let image_format = detect_format(&request.path)?;
let angle = detect_orientation(&request.path).unwrap_or(0.0);
img_sender.send(LoadResult::Start { req_id: request.req_id, metadata }).unwrap();
match image_format {
ImgFormat::Image(ImageFormat::Gif) => {
let frames = load_gif(&request.path, request.req_id)?;
for frame in frames {
img_sender.send(frame?).unwrap();
}
}
ImgFormat::Image(ImageFormat::Png) => {
let file = fs::File::open(&request.path)?;
let decoder = PngDecoder::new(file)?;
if decoder.is_apng() {
for frame in load_animation(request.req_id, decoder.apng())? {
img_sender.send(frame?).unwrap();
}
} else {
let image = load_image(&request.path, ImageFormat::Png)?;
img_sender
.send(LoadResult::Frame {
req_id: request.req_id,
image,
delay_nano: 0,
angle,
})
.unwrap();
}
}
ImgFormat::Image(image_format) => {
let image = load_image(&request.path, image_format)?;
img_sender
.send(LoadResult::Frame {
req_id: request.req_id,
image,
delay_nano: 0,
angle,
})
.unwrap();
}
#[cfg(feature = "avif")]
ImgFormat::Avif => {
let buf = fs::read(&request.path)?;
let image = libavif_image::read(&buf)?.to_rgba();
img_sender
.send(LoadResult::Frame {
req_id: request.req_id,
image,
delay_nano: 0,
angle,
})
.unwrap();
}
}
Ok(())
}
img_sender
.send(match try_load_and_send(img_sender, &request) {
Ok(()) => LoadResult::Done { req_id: request.req_id },
Err(error) => {
eprintln!(
"Request #{}: Error occurred while loading file {:?}\n {}",
request.req_id, request.path, error,
);
LoadResult::Failed { req_id: request.req_id }
}
})
.unwrap();
}
}
impl Drop for ImageLoader {
fn drop(&mut self) {
self.running.store(false, Ordering::Release);
if let Some(join_handles) = self.join_handles.take() {
for _ in join_handles.iter() {
self.path_tx.send(LoadRequest { req_id: 0, path: PathBuf::from("") }).unwrap();
}
for handle in join_handles.into_iter() {
if let Err(err) = handle.join() {
eprintln!("Error occurred while joining handle {:?}", err);
}
}
}
}
}