use crate::config::OutputConfig;
#[cfg(feature = "metadata")]
use crate::exif::guess_pano_angles_from_bytes;
use crate::{
GeneratedTiles, OutputFormat, PannellumConfig, TilerConfig, TilerError, generate_cube_faces,
generate_pannellum_config, generate_pyramid, save_image,
};
use image::RgbImage;
use rayon::prelude::*;
use std::fs;
use std::fs::File;
use std::path::Path;
#[derive(Debug, Clone, PartialEq)]
pub struct TiledPanoramaOutput {
pub generated_tiles: GeneratedTiles,
pub pannellum_config: PannellumConfig,
pub actual_cube_size: u32,
}
pub fn process_panorama(
src_image: &RgbImage,
config: &TilerConfig,
) -> Result<TiledPanoramaOutput, TilerError> {
let (width, _) = src_image.dimensions();
if !config.angles.v_offset.is_finite() {
return Err(TilerError::InvalidConfig(
"The vertical offset `v_offset` must be a finite number".to_owned(),
));
}
if !config.angles.haov.is_finite()
|| !config.angles.vaov.is_finite()
|| config.angles.haov <= 0.0
|| config.angles.vaov <= 0.0
{
return Err(TilerError::InvalidConfig(
"Both `haov` and `vaov` must be positive, finite numbers".to_owned(),
));
}
if config.output.tile_size == 0 {
return Err(TilerError::InvalidConfig(
"Output `tile_size` must be greater than 0".to_owned(),
));
}
let actual_cube_size = if config.output.cube_size != 0 {
config.output.cube_size
} else {
let ratio = 360.0 / config.angles.haov;
let computed = ratio * f64::from(width) / std::f64::consts::PI;
8 * ((computed / 8.0) as u32)
};
let clamped_tile_size = config.output.tile_size.min(actual_cube_size);
if actual_cube_size == 0 {
return Err(TilerError::InvalidConfig(
"Resolved cube size must be greater than 0".to_owned(),
));
}
let faces = generate_cube_faces(src_image, config, actual_cube_size);
let generated_tiles = generate_pyramid(&faces, config, clamped_tile_size, actual_cube_size)?;
let p_config = generate_pannellum_config(
config,
&generated_tiles,
clamped_tile_size,
actual_cube_size,
);
Ok(TiledPanoramaOutput {
pannellum_config: p_config,
generated_tiles,
actual_cube_size,
})
}
pub fn save_to_disk(
pano: &TiledPanoramaOutput,
output_dir: &Path,
output_format: OutputFormat,
quality: u8,
) -> Result<(), TilerError> {
fs::create_dir_all(output_dir)?;
let ext = output_format.to_extension();
for level in 1..=pano.generated_tiles.levels {
let level_dir = output_dir.join(level.to_string());
fs::create_dir_all(&level_dir)?;
}
pano.generated_tiles
.tiles
.par_iter()
.try_for_each(|tile| -> Result<(), TilerError> {
let filename = format!("{}{}_{}.{}", tile.face, tile.row, tile.col, ext);
let filepath = output_dir.join(tile.level.to_string()).join(filename);
save_image(&tile.image, &filepath, output_format, quality)?;
Ok(())
})?;
if !pano.generated_tiles.fallback_tiles.is_empty() {
let fallback_dir = output_dir.join("fallback");
fs::create_dir_all(&fallback_dir)?;
pano.generated_tiles
.fallback_tiles
.par_iter()
.try_for_each(|fallback| -> Result<(), TilerError> {
let filename = format!("{}.{}", fallback.face, ext);
let filepath = fallback_dir.join(filename);
save_image(&fallback.image, &filepath, output_format, quality)?;
Ok(())
})?;
}
let config_path = output_dir.join("config.json");
let file = File::create(config_path)?;
serde_json::to_writer_pretty(file, &pano.pannellum_config)?;
Ok(())
}
#[cfg(feature = "metadata")]
pub fn tile_panorama_with_guessed_angles(
input_file: &Path,
output_dir: &Path,
output_config: Option<OutputConfig>,
) -> Result<(), TilerError> {
let bytes = fs::read(input_file)?;
let config = TilerConfig {
angles: guess_pano_angles_from_bytes(&bytes)?,
output: output_config.unwrap_or_default(),
};
let dynamic_img = image::load_from_memory(&bytes)?;
let rgb_img = dynamic_img.to_rgb8();
let pano_output = process_panorama(&rgb_img, &config)?;
save_to_disk(
&pano_output,
output_dir,
config.output.format,
config.output.quality,
)?;
Ok(())
}
pub fn tile_panorama(
input_file: &Path,
output_dir: &Path,
config: &TilerConfig,
) -> Result<(), TilerError> {
let bytes = fs::read(input_file)?;
let dynamic_img = image::load_from_memory(&bytes)?;
let rgb_img = dynamic_img.to_rgb8();
let pano_output = process_panorama(&rgb_img, config)?;
save_to_disk(
&pano_output,
output_dir,
config.output.format,
config.output.quality,
)?;
Ok(())
}