use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
use std::sync::Arc;
use image::codecs::jpeg::JpegEncoder;
use log::debug;
use crate::encoder::retiler::{Retiler, TileSaver};
use crate::errors::image_error_to_io_error;
use crate::iiif::tile_info;
use crate::tile::Tile;
use crate::{Vec2d, ZoomError};
use super::{Encoder, SourceLevel};
pub struct IiifEncoder {
retiler: Retiler<IIIFTileSaver>,
root_path: PathBuf,
direct_tile_saver: Arc<IIIFTileSaver>,
direct_levels: Vec<SourceLevel>,
current_level: Option<SourceLevel>,
}
impl IiifEncoder {
pub fn new(destination: PathBuf, size: Vec2d, quality: u8) -> Result<Self, ZoomError> {
let _ = std::fs::remove_file(&destination);
debug!("Creating IIIF directory at {:?}", destination);
std::fs::create_dir(&destination)?;
let tile_saver = Arc::new(IIIFTileSaver {
root_path: destination.clone(),
quality,
});
let tile_size = Vec2d::square(512);
Ok(IiifEncoder {
retiler: Retiler::new(size, tile_size, Arc::clone(&tile_saver), 1),
root_path: destination,
direct_tile_saver: tile_saver,
direct_levels: Vec::new(),
current_level: None,
})
}
}
impl Encoder for IiifEncoder {
fn begin_level(&mut self, level: SourceLevel) -> io::Result<()> {
self.current_level = Some(level);
self.direct_levels.push(level);
Ok(())
}
fn add_tile(&mut self, tile: Tile) -> io::Result<()> {
if let Some(level) = self.current_level {
self.direct_tile_saver
.save_tile_at_scale(level.scale_factor, tile)
} else {
self.retiler.add_tile(&tile)
}
}
fn finalize(&mut self) -> io::Result<()> {
if self.direct_levels.is_empty() {
self.retiler.finalize();
}
let scale_factors = if self.direct_levels.is_empty() {
(0..self.retiler.level_count())
.map(|n| 2u32.pow(n))
.collect::<Vec<_>>()
} else {
self.direct_levels
.iter()
.map(|level| level.scale_factor)
.collect::<Vec<_>>()
};
let tile_size = self
.direct_levels
.iter()
.find_map(|level| level.tile_size)
.unwrap_or(self.retiler.tile_size);
let image_info = tile_info::ImageInfo {
context: Some("http://iiif.io/api/image/3/context.json".to_string()),
iiif_type: Some("ImageService3".to_string()),
protocol: Some("http://iiif.io/api/image".to_string()),
profile: Some(tile_info::Profile::Reference("level0".to_string())),
id: Some(".".to_string()),
width: self.size().x,
height: self.size().y,
qualities: Some(vec!["default".into()]),
formats: Some(vec!["jpg".into()]),
tiles: Some(vec![tile_info::TileInfo {
width: tile_size.x,
height: Some(tile_size.y),
scale_factors,
}]),
..Default::default()
};
let info_json_str = serde_json::to_string(&image_info)?;
let info_json_path = self.root_path.join("info.json");
let viewer_path = self.root_path.join("viewer.html");
debug!("Writing iiif metadata to {info_json_path:?}");
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(info_json_path)?
.write_all(info_json_str.as_bytes())?;
debug!("Writing viewer page to {viewer_path:?}");
let viewer_buf = include_str!("./viewer_files/viewer.html")
.replace(
"/*DEZOOMIFY_SEADRAGON*/",
include_str!("./viewer_files/openseadragon.min.js"),
)
.replace("{/*DEZOOMIFY_TILE_SOURCE*/}", &info_json_str);
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(viewer_path)?
.write_all(viewer_buf.as_bytes())?;
Ok(())
}
fn size(&self) -> Vec2d {
self.retiler.size()
}
}
struct IIIFTileSaver {
root_path: PathBuf,
quality: u8,
}
impl IIIFTileSaver {
fn save_tile_at_scale(&self, scale_factor: u32, tile: Tile) -> io::Result<()> {
self.save_tile_region(
tile.position * scale_factor,
tile.size() * scale_factor,
tile.size(),
tile,
)
}
fn save_tile_region(
&self,
full_position: Vec2d,
full_size: Vec2d,
tile_size: Vec2d,
tile: Tile,
) -> io::Result<()> {
let region = format!(
"{},{},{},{}",
full_position.x, full_position.y, full_size.x, full_size.y
);
let tile_size_str = format!("{},{}", tile_size.x, tile_size.y);
let rotation = "0";
let filename = "default.jpg";
let mut image_dir_path = self.root_path.clone();
image_dir_path.push(region);
image_dir_path.push(tile_size_str);
image_dir_path.push(rotation);
let image_path = image_dir_path.join(filename);
debug!("Writing tile to {image_path:?}");
std::fs::create_dir_all(&image_dir_path)?;
let file = &mut BufWriter::new(File::create(&image_path)?);
let jpeg_writer = JpegEncoder::new_with_quality(file, self.quality);
tile.image
.write_with_encoder(jpeg_writer)
.map_err(image_error_to_io_error)
}
}
impl TileSaver for IIIFTileSaver {
fn save_tile(&self, size: Vec2d, tile: Tile) -> io::Result<()> {
self.save_tile_region(tile.position, size, tile.size(), tile)
}
}