Skip to content

Commit

Permalink
Add preliminary support for the IIIF format
Browse files Browse the repository at this point in the history
  • Loading branch information
lovasoa committed Aug 13, 2019
1 parent 3768eeb commit 6d005d3
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 82 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dezoomify-rs"
version = "1.1.3"
version = "1.2.0"
authors = ["Ophir LOJKINE <ophir.lojkine@auto-grid.com>"]
edition = "2018"
license-file = "LICENSE"
Expand All @@ -16,11 +16,12 @@ lazy_static = "1.3"
itertools = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
serde-xml-rs = "0.3"
serde_json = "1.0"
rayon = "1.1"
block-modes = "0.3"
aes = "0.3"
hmac = "0.7"
sha-1 = "0.8"
base64 = "0.10"
serde-xml-rs = "0.3"
indicatif = "0.11"
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ because of memory constraints.

The following dezoomers are currently available:
- [**zoomify**](#zoomify) supports the popular zoomable image format *Zoomify*.
- [**IIIF**](#IIIF) supports the widely used International Image Interoperability Framework format.
- [**Google Arts & Culture**](#google-arts-culture) supports downloading images from
[artsandculture.google.com](https://artsandculture.google.com/);
- [**custom**](#Custom) for advanced users.
Expand Down Expand Up @@ -83,6 +84,12 @@ If the image tile URLs have the form
then the URL to enter is
`http://example.com/path/to/ImageProperties.xml`.

### IIIF

The IIIF dezoomer takes the URL of an
[`info.json`](https://iiif.io/api/image/2.1/#image-information) file as input.
You can find this url in your browser's network inspector when loading the image.

## Command-line options

When using dezoomify-rs from the command-line
Expand Down
1 change: 1 addition & 0 deletions src/auto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub fn all_dezoomers(include_generic: bool) -> Vec<Box<dyn Dezoomer>> {
Box::new(crate::custom_yaml::CustomDezoomer::default()),
Box::new(crate::google_arts_and_culture::GAPDezoomer::default()),
Box::new(crate::zoomify::ZoomifyDezoomer::default()),
Box::new(crate::iiif::IIIF::default()),
];
if include_generic {
dezoomers.push(Box::new(AutoDezoomer::default()))
Expand Down
77 changes: 3 additions & 74 deletions src/dezoomer.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::collections::HashMap;
use std::error::Error;
use std::fmt::Debug;
use std::ops::{Add, Div, Mul, Sub};
use std::str::FromStr;

use custom_error::custom_error;

pub use super::Vec2d;
use super::ZoomError;

pub struct DezoomerInput {
Expand Down Expand Up @@ -160,80 +160,9 @@ impl<T: TilesRect> TileProvider for T {
}
}

#[derive(Debug, PartialEq, Default, Clone, Copy)]
pub struct Vec2d {
pub x: u32,
pub y: u32,
}

impl Vec2d {
pub fn max(self, other: Vec2d) -> Vec2d {
Vec2d {
x: self.x.max(other.x),
y: self.y.max(other.y),
}
}
pub fn min(self, other: Vec2d) -> Vec2d {
Vec2d {
x: self.x.min(other.x),
y: self.y.min(other.y),
}
}
pub fn ceil_div(self, other: Vec2d) -> Vec2d {
let x = self.x / other.x + if self.x % other.x == 0 { 0 } else { 1 };
let y = self.y / other.y + if self.y % other.y == 0 { 0 } else { 1 };
Vec2d { x, y }
}
}

impl std::fmt::Display for Vec2d {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "x={} y={}", self.x, self.y)
}
}

impl Add<Vec2d> for Vec2d {
type Output = Vec2d;

fn add(self, rhs: Vec2d) -> Self::Output {
Vec2d {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}

impl Sub<Vec2d> for Vec2d {
type Output = Vec2d;

fn sub(self, rhs: Vec2d) -> Self::Output {
Vec2d {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}

impl Mul<Vec2d> for Vec2d {
type Output = Vec2d;

fn mul(self, rhs: Vec2d) -> Self::Output {
Vec2d {
x: self.x * rhs.x,
y: self.y * rhs.y,
}
}
}

impl Div<Vec2d> for Vec2d {
type Output = Vec2d;

fn div(self, rhs: Vec2d) -> Self::Output {
Vec2d {
x: self.x / rhs.x,
y: self.y / rhs.y,
}
}
pub fn max_size_in_rect(position: Vec2d, tile_size: Vec2d, canvas_size: Vec2d) -> Vec2d {
(position + tile_size).min(canvas_size) - position
}

#[derive(Debug, PartialEq, Clone)]
Expand Down
3 changes: 2 additions & 1 deletion src/google_arts_and_culture/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::error::Error;
use std::sync::Arc;

use tile_info::{PageInfo, TileInfo};

use crate::dezoomer::*;
use crate::google_arts_and_culture::tile_info::{PageInfo, TileInfo};

mod decryption;
mod tile_info;
Expand Down
135 changes: 135 additions & 0 deletions src/iiif/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use std::sync::Arc;

use custom_error::custom_error;
use tile_info::ImageInfo;

use crate::dezoomer::*;

mod tile_info;

#[derive(Default)]
pub struct IIIF;

custom_error! {pub IIIFError
JsonError{source: serde_json::Error} = "Invalid IIIF info.json file: {source}"
}

impl From<IIIFError> for DezoomerError {
fn from(err: IIIFError) -> Self {
DezoomerError::Other { source: err.into() }
}
}

impl Dezoomer for IIIF {
fn name(&self) -> &'static str {
"iiif"
}

fn zoom_levels(&mut self, data: &DezoomerInput) -> Result<ZoomLevels, DezoomerError> {
self.assert(data.uri.ends_with("/info.json"))?;
let contents = data.with_contents()?.contents;
Ok(zoom_levels(contents)?)
}
}

fn zoom_levels(raw_info: &[u8]) -> Result<ZoomLevels, IIIFError> {
let image_info: ImageInfo = serde_json::from_slice(raw_info)?;
let img = Arc::new(image_info);
let default_tiles = vec![Default::default()];
let tiles = img.tiles.as_ref().unwrap_or(&default_tiles);
let levels = tiles
.iter()
.flat_map(|tile_info| {
let tile_size = Vec2d {
x: tile_info.width,
y: tile_info.height.unwrap_or(tile_info.width),
};
let page_info = &img; // Required to allow the move
tile_info
.scale_factors
.iter()
.map(move |&scale_factor| IIIFZoomLevel {
scale_factor,
tile_size,
page_info: Arc::clone(page_info),
})
})
.into_zoom_levels();
Ok(levels)
}

struct IIIFZoomLevel {
scale_factor: u32,
tile_size: Vec2d,
page_info: Arc<ImageInfo>,
}

impl TilesRect for IIIFZoomLevel {
fn size(&self) -> Vec2d {
Vec2d {
x: self.page_info.width / self.scale_factor,
y: self.page_info.height / self.scale_factor,
}
}

fn tile_size(&self) -> Vec2d {
self.tile_size
}

fn tile_url(&self, col_and_row_pos: Vec2d) -> String {
let scaled_tile_size = self.tile_size * self.scale_factor;
let xy_pos = col_and_row_pos * scaled_tile_size;
let scaled_tile_size = max_size_in_rect(xy_pos, scaled_tile_size, self.size() * self.scale_factor);
let tile_size = scaled_tile_size / self.scale_factor;
format!(
"{base}/{x},{y},{img_w},{img_h}/{tile_w},{tile_h}/{rotation}/{quality}.{format}",
base = self.page_info.id,
x = xy_pos.x,
y = xy_pos.y,
img_w = scaled_tile_size.x,
img_h = scaled_tile_size.y,
tile_w = tile_size.x,
tile_h = tile_size.y,
rotation = 0,
quality = "default",
format = "jpg"
)
}
}

impl std::fmt::Debug for IIIFZoomLevel {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"IIIF image with {}x{} tiles",
self.tile_size.x, self.tile_size.y
)
}
}

#[test]
fn test_tiles() {
let data = br#"{
"@context" : "http://iiif.io/api/image/2/context.json",
"@id" : "http://www.asmilano.it/fast/iipsrv.fcgi?IIIF=/opt/divenire/files/./tifs/05/36/536765.tif",
"protocol" : "http://iiif.io/api/image",
"width" : 15001,
"height" : 48002,
"tiles" : [
{ "width" : 512, "height" : 512, "scaleFactors" : [ 1, 2, 4, 8, 16, 32, 64, 128 ] }
],
"profile" : [
"http://iiif.io/api/image/2/level1.json",
{ "formats" : [ "jpg" ],
"qualities" : [ "native","color","gray" ],
"supports" : ["regionByPct","sizeByForcedWh","sizeByWh","sizeAboveFull","rotationBy90s","mirroring","gray"] }
]
}"#;
let levels = zoom_levels(data).unwrap();
let tiles: Vec<String> = levels[6].tiles().into_iter()
.map(|t| t.unwrap().url)
.collect();
assert_eq!(tiles, vec![
"",
])
}
51 changes: 51 additions & 0 deletions src/iiif/tile_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use serde::Deserialize;

#[derive(Debug, Deserialize, PartialEq)]
pub struct ImageInfo {
#[serde(rename = "@id")]
pub id: String,
pub width: u32,
pub height: u32,
pub tiles: Option<Vec<TileInfo>>,
}

#[derive(Debug, Deserialize, PartialEq)]
pub struct TileInfo {
pub width: u32,
pub height: Option<u32>,
#[serde(rename = "scaleFactors")]
pub scale_factors: Vec<u32>,
}

impl Default for TileInfo {
fn default() -> Self {
TileInfo {
width: 512,
height: None,
scale_factors: vec![1],
}
}
}

#[test]
fn test_deserialisation() {
let _: ImageInfo = serde_json::from_str(
r#"{
"@context" : "http://iiif.io/api/image/2/context.json",
"@id" : "http://www.example.org/image-service/abcd1234/1E34750D-38DB-4825-A38A-B60A345E591C",
"protocol" : "http://iiif.io/api/image",
"width" : 6000,
"height" : 4000,
"sizes" : [
{"width" : 150, "height" : 100},
{"width" : 600, "height" : 400},
{"width" : 3000, "height": 2000}
],
"tiles": [
{"width" : 512, "scaleFactors" : [1,2,4,8,16]}
],
"profile" : [ "http://iiif.io/api/image/2/level2.json" ]
}"#,
)
.unwrap();
}
10 changes: 6 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ use structopt::StructOpt;
use custom_error::custom_error;
use dezoomer::{Dezoomer, DezoomerError, DezoomerInput, ZoomLevels};
use dezoomer::TileReference;
use dezoomer::Vec2d;
pub use vec2d::Vec2d;

use crate::dezoomer::ZoomLevel;
use crate::dezoomer::{max_size_in_rect, ZoomLevel};

mod vec2d;
mod auto;
mod custom_yaml;
mod dezoomer;
mod auto;
mod google_arts_and_culture;
mod iiif;
mod zoomify;

#[derive(StructOpt, Debug)]
Expand Down Expand Up @@ -343,7 +345,7 @@ impl Canvas {
}
fn add_tile(self: &mut Self, tile: &Tile) -> Result<(), ZoomError> {
let Vec2d { x: xmax, y: ymax } =
(tile.position + tile.size()).min(self.size()) - tile.position;
max_size_in_rect(tile.position, tile.size(), self.size());
let sub_tile = tile.image.view(0, 0, xmax, ymax);
let Vec2d { x, y } = tile.position;
let success = self.image.copy_from(&sub_tile, x, y);
Expand Down
Loading

0 comments on commit 6d005d3

Please sign in to comment.