imogen 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/lib/imogen/dzi.rb +52 -0
- data/lib/imogen/iiif/region.rb +1 -1
- data/lib/imogen/iiif/size.rb +1 -1
- data/lib/imogen/iiif/tiles.rb +38 -0
- data/lib/imogen/iiif.rb +24 -3
- data/lib/imogen/zoomable.rb +8 -3
- data/lib/imogen.rb +2 -6
- data/spec/unit/imogen_iiif_region_spec.rb +2 -1
- data/spec/unit/imogen_iiif_rotate_spec.rb +8 -6
- data/spec/unit/imogen_iiif_tiles_spec.rb +78 -0
- metadata +11 -8
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YjdlYmVmMjIwZmFhMzNmYTdmMjNiOTFiYjM2ZjNmZTllMjYyZTRmMw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YjU0NmU3NDdkZTE3YWI0NTdlMDUyMzhhYjk0OTI0ODI1YjIyYzAyYg==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MWQ1ODU2NTE3OWRlN2MwZTNlOWYzYjQ0NmI0ZDhmNTBjZmMzMDdmZWYwNmMy
|
10
|
+
NDAwNzU4Y2RhMTE0MjBlYWIxMzg3ZGQxZWU0Yjc2MjU3ZjBhYzY5NjE2NGY4
|
11
|
+
YTAxNjAzZTBkZGNlZTYwYzZiM2Y0NzFlOWIxNTliOTYzZmEzN2Y=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZWE1ZjZiOGNjYzE3OWJlZjA5YWRlMWRlMDQzOWE2NTAxMzVmYjAzYmQ3M2Nm
|
14
|
+
NjVlZjY2NmY2YmE3NWU3MzE2NmQ2ZjhkNjYwMzY2ODA3MWQ3MzRkNTY5Mjg4
|
15
|
+
MDJjZDIxMzcyZjVmZTgwNDZiMTJiZjJhOTMzYjQ1MmU5YmRiNzY=
|
data/lib/imogen/dzi.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'imogen/zoomable'
|
2
|
+
require 'imogen/iiif'
|
3
|
+
module Imogen
|
4
|
+
module Dzi
|
5
|
+
def self.convert(img,dest_dir,format=:jpeg,opts={})
|
6
|
+
override = opts[:override]
|
7
|
+
|
8
|
+
end
|
9
|
+
def self.iiif_paths(img,tile_size=128,format=:jpeg)
|
10
|
+
width, height = img.width, img.height
|
11
|
+
max_levels = Imogen::Zoomable.max_levels_for(width, height)
|
12
|
+
results = {}
|
13
|
+
max_levels.downto(0) do |level|
|
14
|
+
c_ratio = 2**(max_levels-level)
|
15
|
+
|
16
|
+
tile_side = tile_size*c_ratio
|
17
|
+
x, col = 0, 0
|
18
|
+
while x < width
|
19
|
+
y, row = 0, 0
|
20
|
+
while y < height
|
21
|
+
results["#{level}/#{col_count}_#{row_count}"] = iiif_path_for_dzi(level,max_levels,col,row,tile_size,format)
|
22
|
+
y += tile_side
|
23
|
+
row += 1
|
24
|
+
end
|
25
|
+
x += tile_side
|
26
|
+
col += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
def self.iiif_opts_for_dzi(level,max_levels,col,row,tile_size=128,format=:jpeg)
|
32
|
+
level = level.to_i
|
33
|
+
max_levels = max_levels.to_i
|
34
|
+
c_ratio = 2**(max_levels-level)
|
35
|
+
|
36
|
+
tile_side = tile_size*c_ratio
|
37
|
+
x = col * c_ratio
|
38
|
+
y = row * c_ratio
|
39
|
+
{
|
40
|
+
region: "#{x},#{y},#{tile_side},#{tile_side}",
|
41
|
+
size: "!#{tile_size},#{tile_size}",
|
42
|
+
format: format,
|
43
|
+
rotation: 0,
|
44
|
+
quality: :native
|
45
|
+
}
|
46
|
+
end
|
47
|
+
def self.iiif_path_for_dzi(level,max_levels,col,row,tile_size=512,format=:jpeg)
|
48
|
+
opts = iiif_opts_for_dzi(level, max_levels,col,row,tile_size,format)
|
49
|
+
"#{opts[:region]}/#{opts[:size]}/#{opts[:rotation]}/#{opts[:quality]}.#{Imogen::Iiif::FORMATS[format]}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/imogen/iiif/region.rb
CHANGED
@@ -20,7 +20,7 @@ class Region < Transform
|
|
20
20
|
]
|
21
21
|
elsif md = /^(\d+),(\d+),(\d+),(\d+)$/.match(region)
|
22
22
|
p = [Integer(md[1]),Integer(md[2]),Integer(md[3]),Integer(md[4])]
|
23
|
-
if p[
|
23
|
+
if p[2] == 0 or p[3] == 0
|
24
24
|
raise BadRequest.new("Invalid region: #{region}")
|
25
25
|
end
|
26
26
|
e = [
|
data/lib/imogen/iiif/size.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'imogen/zoomable'
|
2
|
+
module Imogen
|
3
|
+
module Iiif
|
4
|
+
module Tiles
|
5
|
+
def self.scale_factor_for(*dims)
|
6
|
+
Imogen::Zoomable.levels_for(*dims)**2
|
7
|
+
end
|
8
|
+
def self.for(img,dest_dir,format=:jpeg,tile_size=128,override=false)
|
9
|
+
width, height = img.width, img.height
|
10
|
+
max_level = Imogen::Zoomable.levels_for(width,height,tile_size)
|
11
|
+
max_level.downto(0) do |level|
|
12
|
+
scale = 0.5**level
|
13
|
+
level_width = (img.width*scale).ceil
|
14
|
+
level_width = (img.height*scale).ceil
|
15
|
+
region_size = (tile_size / scale).ceil
|
16
|
+
region_width = (scale * img.width).ceil
|
17
|
+
region_height = (scale * img.height).ceil
|
18
|
+
x, col = 0, 0
|
19
|
+
while x < width
|
20
|
+
y, row = 0, 0
|
21
|
+
while y < height
|
22
|
+
region = "#{x},#{y},#{[width-x,region_size].min},#{[height-y,region_size].min}"
|
23
|
+
size = "full"
|
24
|
+
dest_path = File.join(dest_dir,region,size,'0',"native.#{Imogen::Iiif::FORMATS[format]}")
|
25
|
+
unless File.exists? dest_path or override
|
26
|
+
yield(img,dest_path,format,Imogen::Iiif.path_to_opts(dest_path,dest_dir))
|
27
|
+
end
|
28
|
+
y += region_size
|
29
|
+
row += 1
|
30
|
+
end
|
31
|
+
x += region_size
|
32
|
+
col += 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/imogen/iiif.rb
CHANGED
@@ -23,7 +23,7 @@ module Imogen
|
|
23
23
|
module Quality
|
24
24
|
VALUES = [:native, :color, :grey, :bitonal]
|
25
25
|
def self.get(quality=:native)
|
26
|
-
q = quality.to_sym
|
26
|
+
q = (quality || :native).to_sym
|
27
27
|
raise BadRequest.new("bad quality #{quality}") unless VALUES.include? q
|
28
28
|
return (q == :native or q == :color) ? nil : q
|
29
29
|
end
|
@@ -41,15 +41,20 @@ module Imogen
|
|
41
41
|
autoload :Region, 'imogen/iiif/region'
|
42
42
|
autoload :Size, 'imogen/iiif/size'
|
43
43
|
autoload :Rotation, 'imogen/iiif/rotation'
|
44
|
+
autoload :Tiles, 'imogen/iiif/tiles'
|
44
45
|
|
45
|
-
FORMATS =
|
46
|
-
|
46
|
+
FORMATS = {jpeg: 'jpg', jpg: 'jpg', png: 'png'}
|
47
|
+
EXTENSIONS = {'jpg' => :jpeg, 'png' => :png}
|
48
|
+
def self.convert(img, dest_path, format=nil, opts={})
|
49
|
+
format ||= opts.fetch(:format,:jpeg)
|
50
|
+
format = format.to_sym
|
47
51
|
raise BadRequest.new("bad format #{format}") unless FORMATS.include? format
|
48
52
|
Region.convert(img, opts[:region]) do |region|
|
49
53
|
Size.convert(region, opts[:size]) do |size|
|
50
54
|
Rotation.convert(size, opts[:rotation]) do |rotation|
|
51
55
|
Quality.convert(rotation, opts[:quality]) do |quality|
|
52
56
|
dst = FreeImage::File.new(dest_path)
|
57
|
+
format = :jpeg if format == :jpg
|
53
58
|
if (img.color_type == :rgb)
|
54
59
|
quality.convert_to_24bits {|result| dst.save(result, format)}
|
55
60
|
else
|
@@ -60,5 +65,21 @@ module Imogen
|
|
60
65
|
end
|
61
66
|
end
|
62
67
|
end
|
68
|
+
def self.path_to_opts(path,parent_dir)
|
69
|
+
if parent_dir and path.start_with? parent_dir
|
70
|
+
path = path.sub(parent_dir,'')
|
71
|
+
end
|
72
|
+
path = path[1..-1] if path =~ /^\//
|
73
|
+
parts = path.split('/')
|
74
|
+
quality = parts[-1].split('.')[0].to_sym
|
75
|
+
format = EXTENSIONS[parts[-1].split('.')[1]]
|
76
|
+
{
|
77
|
+
region: parts[0],
|
78
|
+
size: parts[1],
|
79
|
+
rotation: parts[2],
|
80
|
+
quality: quality,
|
81
|
+
format: format
|
82
|
+
}
|
83
|
+
end
|
63
84
|
end
|
64
85
|
end
|
data/lib/imogen/zoomable.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
module Imogen
|
2
2
|
module Zoomable
|
3
|
+
# levels for hypothetical 1x1 tiles
|
4
|
+
def self.max_levels_for(*dims)
|
5
|
+
return Math.log2(dims[0..1].max).ceil
|
6
|
+
end
|
7
|
+
# levels for width,height, tile_size=128
|
3
8
|
def self.levels_for(*dims)
|
4
9
|
max = dims[0..1].max || 0
|
5
|
-
return 0 if max
|
6
|
-
|
7
|
-
Math.log2(
|
10
|
+
return 0 if max == 0
|
11
|
+
tile_size = dims[2] || 128
|
12
|
+
return Math.log2(dims[0..1].max.to_f / tile_size).ceil
|
8
13
|
end
|
9
14
|
def self.convert(img, dest_path)
|
10
15
|
dst = FreeImage::File.new(dest_path)
|
data/lib/imogen.rb
CHANGED
@@ -85,9 +85,7 @@ module Imogen
|
|
85
85
|
result
|
86
86
|
end
|
87
87
|
|
88
|
-
def self.image(src_path)
|
89
|
-
|
90
|
-
flags = 0
|
88
|
+
def self.image(src_path, flags=0)
|
91
89
|
|
92
90
|
fif = format_from(src_path)
|
93
91
|
if ((fif != :unknown) and FreeImage.FreeImage_FIFSupportsReading(fif))
|
@@ -97,9 +95,7 @@ module Imogen
|
|
97
95
|
end
|
98
96
|
return nil
|
99
97
|
end
|
100
|
-
def self.with_image(src_path, &block)
|
101
|
-
|
102
|
-
flags = 0
|
98
|
+
def self.with_image(src_path, flags = 0, &block)
|
103
99
|
|
104
100
|
fif = format_from(src_path)
|
105
101
|
if ((fif != :unknown) and FreeImage.FreeImage_FIFSupportsReading(fif))
|
@@ -17,7 +17,8 @@ describe Imogen::Iiif::Region, type: :unit do
|
|
17
17
|
expect(subject.get("80,15,60,75")).to eql([80,15,140,90])
|
18
18
|
end
|
19
19
|
it "should reject zero-dimension boxes" do
|
20
|
-
expect{subject.get("101,15,
|
20
|
+
expect{subject.get("101,15,0,15")}.to raise_error Imogen::Iiif::BadRequest
|
21
|
+
expect{subject.get("101,15,10,0")}.to raise_error Imogen::Iiif::BadRequest
|
21
22
|
end
|
22
23
|
describe "that exceeds the bounds" do
|
23
24
|
it "should calculate an overlapping region" do
|
@@ -13,17 +13,19 @@ describe Imogen::Iiif::Rotation, type: :unit do
|
|
13
13
|
expect(subject.get("0")).to be_nil
|
14
14
|
expect(subject.get(nil)).to be_nil
|
15
15
|
end
|
16
|
+
# IIIF rotation is opposite FreeImage
|
16
17
|
it "should calculate for positive values" do
|
17
|
-
expect(subject.get("90")).to eql(
|
18
|
+
expect(subject.get("90")).to eql(270)
|
18
19
|
expect(subject.get("180")).to eql(180)
|
19
|
-
expect(subject.get("270")).to eql(
|
20
|
-
expect(subject.get("450")).to eql(
|
20
|
+
expect(subject.get("270")).to eql(90)
|
21
|
+
expect(subject.get("450")).to eql(270)
|
21
22
|
end
|
23
|
+
# IIIF rotation is opposite FreeImage
|
22
24
|
it "should calculate for negative values" do
|
23
|
-
expect(subject.get("-90")).to eql(
|
25
|
+
expect(subject.get("-90")).to eql(90)
|
24
26
|
expect(subject.get("-180")).to eql(180)
|
25
|
-
expect(subject.get("-270")).to eql(
|
26
|
-
expect(subject.get("-450")).to eql(
|
27
|
+
expect(subject.get("-270")).to eql(270)
|
28
|
+
expect(subject.get("-450")).to eql(90)
|
27
29
|
end
|
28
30
|
end
|
29
31
|
it "should reject arbitrary integer and float values" do
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'imogen/iiif'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
describe Imogen::Iiif::Tiles, type: :unit do
|
4
|
+
before(:all) do
|
5
|
+
@test_image = ImageStub.new(175,131)
|
6
|
+
end
|
7
|
+
let(:fullSize) { ["/0,0,175,131/full/0/native.jpg"]}
|
8
|
+
let(:tile128) {
|
9
|
+
[
|
10
|
+
"/0,0,128,128/full/0/native.jpg",
|
11
|
+
"/128,0,47,128/full/0/native.jpg",
|
12
|
+
"/0,128,128,3/full/0/native.jpg",
|
13
|
+
"/128,128,47,3/full/0/native.jpg"
|
14
|
+
]
|
15
|
+
}
|
16
|
+
let(:tile64) {
|
17
|
+
[
|
18
|
+
"/0,0,64,64/full/0/native.jpg",
|
19
|
+
"/64,0,64,64/full/0/native.jpg",
|
20
|
+
"/128,0,47,64/full/0/native.jpg",
|
21
|
+
"/0,64,64,64/full/0/native.jpg",
|
22
|
+
"/64,64,64,64/full/0/native.jpg",
|
23
|
+
"/128,64,47,64/full/0/native.jpg",
|
24
|
+
"/0,128,64,3/full/0/native.jpg",
|
25
|
+
"/64,128,64,3/full/0/native.jpg",
|
26
|
+
"/128,128,47,3/full/0/native.jpg",
|
27
|
+
]
|
28
|
+
}
|
29
|
+
describe '#scale_factor_for' do
|
30
|
+
it 'should calculate for different tile sizes' do
|
31
|
+
width, height = @test_image.width, @test_image.height
|
32
|
+
(0..8).each do |exp|
|
33
|
+
tile_size = 2**exp
|
34
|
+
expected = (8 - exp)**2
|
35
|
+
expect(subject.scale_factor_for(width, height, tile_size)).to eql(expected)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
describe '#for' do
|
40
|
+
it 'should produce a single tile when contained' do
|
41
|
+
expected = fullSize
|
42
|
+
actual = []
|
43
|
+
subject.for(@test_image,'',:jpeg,256) do |img,dest_path,format,opts|
|
44
|
+
actual << dest_path
|
45
|
+
end
|
46
|
+
expect(actual).to eql(expected)
|
47
|
+
end
|
48
|
+
it 'should produce a tileset when half' do
|
49
|
+
expected = fullSize + tile128
|
50
|
+
expected.uniq!
|
51
|
+
actual = []
|
52
|
+
subject.for(@test_image,'',:jpeg,128) do |img,dest_path,format,opts|
|
53
|
+
actual << dest_path
|
54
|
+
end
|
55
|
+
actual.uniq!
|
56
|
+
expect(actual.sort).to eql(expected.sort)
|
57
|
+
end
|
58
|
+
it 'should produce a single tileset' do
|
59
|
+
expected = fullSize + tile128 + tile64
|
60
|
+
expected.uniq!
|
61
|
+
actual = []
|
62
|
+
subject.for(@test_image,'',:jpeg,64) do |img,dest_path,format,opts|
|
63
|
+
actual << dest_path
|
64
|
+
end
|
65
|
+
actual.uniq!
|
66
|
+
expect(actual.sort).to eql(expected.sort)
|
67
|
+
end
|
68
|
+
it 'should produce png when requested' do
|
69
|
+
expected = fullSize.collect {|x| x.sub(/jpg$/,'png')}
|
70
|
+
expected.uniq!
|
71
|
+
actual = []
|
72
|
+
subject.for(@test_image,'',:png,256) do |img,dest_path,format,opts|
|
73
|
+
actual << dest_path
|
74
|
+
end
|
75
|
+
expect(actual).to eql(expected)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
metadata
CHANGED
@@ -1,41 +1,41 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: imogen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Armintor
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-10-
|
11
|
+
date: 2014-10-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-opencv
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ! '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ! '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ! '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ! '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
description:
|
@@ -48,15 +48,18 @@ files:
|
|
48
48
|
- lib/imogen/auto_crop.rb
|
49
49
|
- lib/imogen/auto_crop/box.rb
|
50
50
|
- lib/imogen/auto_crop/edges.rb
|
51
|
+
- lib/imogen/dzi.rb
|
51
52
|
- lib/imogen/iiif.rb
|
52
53
|
- lib/imogen/iiif/region.rb
|
53
54
|
- lib/imogen/iiif/rotation.rb
|
54
55
|
- lib/imogen/iiif/size.rb
|
56
|
+
- lib/imogen/iiif/tiles.rb
|
55
57
|
- lib/imogen/zoomable.rb
|
56
58
|
- spec/spec_helper.rb
|
57
59
|
- spec/unit/imogen_iiif_region_spec.rb
|
58
60
|
- spec/unit/imogen_iiif_rotate_spec.rb
|
59
61
|
- spec/unit/imogen_iiif_size_spec.rb
|
62
|
+
- spec/unit/imogen_iiif_tiles_spec.rb
|
60
63
|
homepage: https://github.com/cul/imogen
|
61
64
|
licenses: []
|
62
65
|
metadata: {}
|
@@ -66,12 +69,12 @@ require_paths:
|
|
66
69
|
- lib
|
67
70
|
required_ruby_version: !ruby/object:Gem::Requirement
|
68
71
|
requirements:
|
69
|
-
- -
|
72
|
+
- - ! '>='
|
70
73
|
- !ruby/object:Gem::Version
|
71
74
|
version: '0'
|
72
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
76
|
requirements:
|
74
|
-
- -
|
77
|
+
- - ! '>='
|
75
78
|
- !ruby/object:Gem::Version
|
76
79
|
version: '0'
|
77
80
|
requirements: []
|