dezoomify_rs/zoomify/
mod.rs

1use std::sync::Arc;
2
3use custom_error::custom_error;
4use image_properties::{ImageProperties, ZoomLevelInfo};
5
6use crate::dezoomer::*;
7
8mod image_properties;
9
10/// Dezoomer for the zoomify image format.
11/// See: http://zoomify.com/
12#[derive(Default)]
13pub struct ZoomifyDezoomer;
14
15impl Dezoomer for ZoomifyDezoomer {
16    fn name(&self) -> &'static str {
17        "zoomify"
18    }
19
20    fn zoom_levels(&mut self, data: &DezoomerInput) -> Result<ZoomLevels, DezoomerError> {
21        self.assert(data.uri.contains("/ImageProperties.xml"))?;
22        let DezoomerInputWithContents { uri, contents } = data.with_contents()?;
23        let levels = load_from_properties(uri, contents)?;
24        Ok(levels)
25    }
26}
27
28custom_error! {pub ZoomifyError
29    XmlError{source: serde_xml_rs::Error} = "Unable to parse ImageProperties.xml: {source}"
30}
31
32impl From<ZoomifyError> for DezoomerError {
33    fn from(err: ZoomifyError) -> Self {
34        DezoomerError::Other { source: err.into() }
35    }
36}
37
38fn load_from_properties(url: &str, contents: &[u8]) -> Result<ZoomLevels, ZoomifyError> {
39    let image_properties: ImageProperties = serde_xml_rs::from_reader(contents)?;
40    let base_url_string = url.split("/ImageProperties.xml").next().unwrap().to_string();
41    let base_url = &Arc::from(base_url_string);
42    let levels: Vec<ZoomLevelInfo> = image_properties.levels();
43    let levels: ZoomLevels = levels.into_iter().enumerate()
44        .map(move |(level, level_info)| ZoomifyLevel {
45            base_url: Arc::clone(base_url),
46            level_info,
47            level,
48        })
49        .into_zoom_levels();
50    Ok(levels)
51}
52
53struct ZoomifyLevel {
54    base_url: Arc<str>,
55    level_info: ZoomLevelInfo,
56    level: usize,
57}
58
59impl TilesRect for ZoomifyLevel {
60    fn size(&self) -> Vec2d {
61        self.level_info.size
62    }
63
64    fn tile_size(&self) -> Vec2d {
65        self.level_info.tile_size
66    }
67
68    fn tile_url(&self, pos: Vec2d) -> String {
69        format!(
70            "{base}/TileGroup{group}/{z}-{x}-{y}.jpg",
71            base = self.base_url,
72            group = self.level_info.tile_group(pos),
73            x = pos.x,
74            y = pos.y,
75            z = self.level
76        )
77    }
78}
79
80impl std::fmt::Debug for ZoomifyLevel {
81    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
82        write!(f, "Zoomify Image")
83    }
84}
85
86#[test]
87fn test_panorama() {
88    let url = "http://x.fr/y/ImageProperties.xml?t";
89    let contents = br#"
90        <IMAGE_PROPERTIES
91            WIDTH="174550" HEIGHT="16991" NUMTILES="61284"
92            NUMIMAGES="1" VERSION="1.8" TILESIZE="256"/>"#;
93    let mut props = load_from_properties(url, contents).unwrap();
94    assert_eq!(props.len(), 11);
95    let level = &mut props[3];
96    let tiles: Vec<String> = level.next_tiles(None).into_iter().map(|t| t.url).collect();
97    assert_eq!(
98        tiles,
99        vec![
100            "http://x.fr/y/TileGroup0/3-0-0.jpg",
101            "http://x.fr/y/TileGroup0/3-1-0.jpg",
102            "http://x.fr/y/TileGroup0/3-2-0.jpg",
103            "http://x.fr/y/TileGroup0/3-3-0.jpg",
104            "http://x.fr/y/TileGroup0/3-4-0.jpg",
105            "http://x.fr/y/TileGroup0/3-5-0.jpg"
106        ]
107    );
108}
109
110#[test]
111fn test_tilegroups() {
112    use std::collections::HashSet;
113    let url = "http://x.fr/y/ImageProperties.xml?t";
114    let contents = br#"<IMAGE_PROPERTIES WIDTH="12000" HEIGHT="9788"
115                                NUMTILES="2477" NUMIMAGES="1" VERSION="1.8" TILESIZE="256"/>"#;
116    let mut props = load_from_properties(url, contents).unwrap();
117    let level = &mut props[5];
118    let tiles: HashSet<String> = level.next_tiles(None).into_iter().map(|t| t.url).collect();
119    assert!(tiles.contains("http://x.fr/y/TileGroup1/5-0-14.jpg"));
120    assert!(tiles.contains("http://x.fr/y/TileGroup2/5-0-15.jpg"));
121}