imogen 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/imogen/iiif/tiles.rb +145 -22
- data/lib/imogen/iiif.rb +3 -1
- data/lib/imogen.rb +13 -22
- data/spec/integration/imogen_iiif_spec.rb +2 -1
- data/spec/integration/imogen_iiif_tiles_spec.rb +94 -0
- data/spec/integration/imogen_spec.rb +9 -11
- data/spec/unit/imogen_iiif_tiles_openseadragon_expectations_spec.rb +106 -0
- data/spec/unit/imogen_iiif_tiles_spec.rb +67 -47
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 032e49d9a8602410ab24b57efc8bd3de1cd523ad41e217bd76affcca385a08aa
|
4
|
+
data.tar.gz: 2ae67a08b37695662dd21af0eedc7c7449423fde11ca20cd02c2f66e00ade5f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a5499840827444cea022ef95ca205f52131084938a6417441eb73898961b726b02229d6f2e14964a77bf4fa24a537ae7b5c582fec1d7bcaf72f556e789dbd23
|
7
|
+
data.tar.gz: 6f53cf3ee9a63cd4906f2f09ede862f31440e573ea0d7033ce37c50d0d661d733dc7cb355e2a137e94f6a3eeb5e91904d2c37c7059039d145722a0b976b656cd
|
data/lib/imogen/iiif/tiles.rb
CHANGED
@@ -2,37 +2,160 @@ require 'imogen/zoomable'
|
|
2
2
|
module Imogen
|
3
3
|
module Iiif
|
4
4
|
module Tiles
|
5
|
-
def self.
|
6
|
-
Imogen::Zoomable.
|
5
|
+
def self.scale_factors_for(full_image_width, full_image_height, tile_size)
|
6
|
+
largest_scale_factor = Imogen::Zoomable.max_levels_for(full_image_width, full_image_height, tile_size)
|
7
|
+
largest_scale_factor -= Math.log2(tile_size / 2) # remove scales smaller than tile size
|
8
|
+
(0..(largest_scale_factor.to_i)).map { |exp| 2.pow(exp) }
|
7
9
|
end
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
|
11
|
+
# Yields many times, for each set of iiif tile raster opts for the given image.
|
12
|
+
# @param img [Image] The image to be analyzed.
|
13
|
+
# @param dest_dir [String] The target output directory for the tile subdirectory hierarchy.
|
14
|
+
# @param format [String] Tile format
|
15
|
+
# @param tile_size [Integer] Tile size
|
16
|
+
# @param quality [String] IIIF quality value (e.g. 'color', 'default')
|
17
|
+
# @yield [image, suggested_dest_path_for_tile, format, raster_opts] Image and tile generation info
|
18
|
+
def self.for(img, dest_dir, format = :jpg, tile_size = 128, quality = 'default')
|
19
|
+
format = :jpg if format.to_s == 'jpeg'
|
20
|
+
|
21
|
+
width = img.width
|
22
|
+
height = img.height
|
23
|
+
|
24
|
+
# For this implementation, we will only support square tiles (same width and height)
|
25
|
+
tile_width = tile_size
|
26
|
+
tile_height = tile_size
|
27
|
+
|
28
|
+
# If the original image dimensions are smaller than the tile_size,
|
29
|
+
# generate a tile for region 'full' and size 'full'.
|
30
|
+
if width < tile_size && height < tile_size
|
31
|
+
raster_opts = {
|
32
|
+
region: 'full',
|
33
|
+
size: 'full',
|
34
|
+
rotation: 0,
|
35
|
+
quality: quality,
|
36
|
+
format: format
|
37
|
+
}
|
38
|
+
|
39
|
+
dest_path = File.join(
|
40
|
+
dest_dir,
|
41
|
+
raster_opts[:region],
|
42
|
+
raster_opts[:size],
|
43
|
+
raster_opts[:rotation].to_s,
|
44
|
+
"#{raster_opts[:quality]}.#{Imogen::Iiif::FORMATS[raster_opts[:format]]}"
|
45
|
+
)
|
46
|
+
yield(img, dest_path, raster_opts['format'], Imogen::Iiif.path_to_opts(dest_path, dest_dir))
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# NOTE: Algorithm below is based on: https://iiif.io/api/image/2.1/#a-implementation-notes
|
51
|
+
self.scale_factors_for(width, height, tile_size).each do |scale_factor|
|
52
|
+
scale_factor_as_float = scale_factor.to_f
|
53
|
+
|
54
|
+
col = 0
|
55
|
+
x = 0
|
19
56
|
while x < width
|
20
|
-
|
57
|
+
row = 0
|
58
|
+
y = 0
|
21
59
|
while y < height
|
22
|
-
region
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
60
|
+
# Calculate region parameters /xr,yr,wr,hr/
|
61
|
+
xr = col * tile_width * scale_factor_as_float
|
62
|
+
yr = row * tile_height * scale_factor_as_float
|
63
|
+
wr = tile_width * scale_factor_as_float
|
64
|
+
if xr + wr > width
|
65
|
+
wr = width - xr
|
66
|
+
end
|
67
|
+
hr = tile_height * scale_factor_as_float
|
68
|
+
if yr + hr > height
|
69
|
+
hr = height - yr
|
70
|
+
end
|
71
|
+
# Calculate size parameters /ws,hs/
|
72
|
+
ws = tile_width
|
73
|
+
if xr + tile_width * scale_factor_as_float > width
|
74
|
+
ws = (width - xr + scale_factor_as_float - 1) / scale_factor_as_float # +scale_factor_as_floatZ-1 in numerator to round up
|
75
|
+
end
|
76
|
+
|
77
|
+
hs = tile_height
|
78
|
+
if yr + tile_height * scale_factor_as_float > height
|
79
|
+
hs = (height - yr + scale_factor_as_float - 1) / scale_factor_as_float
|
27
80
|
end
|
28
|
-
|
81
|
+
|
82
|
+
# If the region width (wr) or region height (hr) go negative, we've gone too far. Break!
|
83
|
+
break if wr <= 0 || hr <= 0
|
84
|
+
|
85
|
+
xr = xr.floor
|
86
|
+
yr = yr.floor
|
87
|
+
wr = wr.floor
|
88
|
+
hr = hr.floor
|
89
|
+
ws = ws.floor
|
90
|
+
hs = hs.floor
|
91
|
+
region = "#{xr},#{yr},#{wr},#{hr}"
|
92
|
+
size = "#{ws},"
|
93
|
+
|
94
|
+
# When tile_width and tile_height are the same, OpenSeadragon only specifies
|
95
|
+
# the width for the size param in image slice URLs, so we will generally not
|
96
|
+
# include the height in the size param string.
|
97
|
+
size += "#{hs}" if tile_width != tile_height
|
98
|
+
|
99
|
+
# Need to do this correction for OpenSeadragon Compatibility, since it asks for "full" in this case.
|
100
|
+
region = 'full' if region == "0,0,#{width},#{height}"
|
101
|
+
|
102
|
+
raster_opts = {
|
103
|
+
region: region,
|
104
|
+
size: size,
|
105
|
+
rotation: 0,
|
106
|
+
quality: quality,
|
107
|
+
format: format
|
108
|
+
}
|
109
|
+
|
110
|
+
dest_path = File.join(
|
111
|
+
dest_dir,
|
112
|
+
raster_opts[:region],
|
113
|
+
raster_opts[:size],
|
114
|
+
raster_opts[:rotation].to_s,
|
115
|
+
"#{raster_opts[:quality]}.#{Imogen::Iiif::FORMATS[raster_opts[:format]]}"
|
116
|
+
)
|
117
|
+
yield(img, dest_path, raster_opts['format'], Imogen::Iiif.path_to_opts(dest_path, dest_dir))
|
118
|
+
|
29
119
|
row += 1
|
120
|
+
y += tile_height
|
30
121
|
end
|
31
|
-
x += region_size
|
32
122
|
col += 1
|
123
|
+
x += tile_width
|
33
124
|
end
|
34
125
|
end
|
35
126
|
end
|
127
|
+
|
128
|
+
# This method should NOT be used right now because it's missing some tiles that we rely on.
|
129
|
+
# This method is just here as a partial vips-dzsave-based implementation that we may want to
|
130
|
+
# build off in a fututure release.
|
131
|
+
# The Imogen::Iiif::Tiles.for method should be used instead, since it generates all of the
|
132
|
+
# expected tiles.
|
133
|
+
# The issue with this method may be related to this:
|
134
|
+
# https://github.com/libvips/libvips/discussions/2036
|
135
|
+
def self.generate_with_vips_dzsave(img, output_dir, format: :jpeg, tile_size: 128, tile_filename_without_extension: 'default')
|
136
|
+
warn "Warning: The generate_with_vips_dzsave is only partially functional and should not "\
|
137
|
+
"be used to generate tiles yet. If you use this method, some IIIF tiles will be missing."
|
138
|
+
format = :jpg if format == :jpeg
|
139
|
+
format = format.to_sym
|
140
|
+
img.dzsave(
|
141
|
+
output_dir,
|
142
|
+
layout: 'iiif',
|
143
|
+
suffix: ".tmp.#{format}",
|
144
|
+
overlap: 0,
|
145
|
+
tile_size: tile_size
|
146
|
+
)
|
147
|
+
|
148
|
+
# Update tile names with desired value
|
149
|
+
Dir[File.join(output_dir, "**/*.tmp.#{format}")].each do |file_path|
|
150
|
+
new_name = File.join(File.dirname(file_path), "#{tile_filename_without_extension}.#{format}")
|
151
|
+
File.rename(file_path, new_name)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Clean up unused additional dzsave files
|
155
|
+
['info.json', 'vips-properties.xml'].each do |unnecessary_file_name|
|
156
|
+
File.delete(file_to_delete)
|
157
|
+
end
|
158
|
+
end
|
36
159
|
end
|
37
160
|
end
|
38
|
-
end
|
161
|
+
end
|
data/lib/imogen/iiif.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Imogen
|
2
2
|
module Iiif
|
3
|
-
class BadRequest <
|
3
|
+
class BadRequest < StandardError; end
|
4
4
|
class Transform
|
5
5
|
def initialize(src)
|
6
6
|
@width = 0
|
@@ -45,6 +45,7 @@ module Imogen
|
|
45
45
|
|
46
46
|
FORMATS = {jpeg: 'jpg', jpg: 'jpg', png: 'png', jp2: 'jp2'}
|
47
47
|
EXTENSIONS = {'jpg' => :jpeg, 'png' => :png, 'jp2' => :jp2}
|
48
|
+
|
48
49
|
def self.convert(img, dest_path, format=nil, opts={})
|
49
50
|
format ||= opts.fetch(:format,:jpeg)
|
50
51
|
format = format.to_sym
|
@@ -60,6 +61,7 @@ module Imogen
|
|
60
61
|
end
|
61
62
|
end
|
62
63
|
end
|
64
|
+
|
63
65
|
def self.path_to_opts(path,parent_dir)
|
64
66
|
if parent_dir and path.start_with? parent_dir
|
65
67
|
path = path.sub(parent_dir,'')
|
data/lib/imogen.rb
CHANGED
@@ -38,34 +38,25 @@ module Imogen
|
|
38
38
|
|
39
39
|
# @param [String] src_path The local file path to the image.
|
40
40
|
# @param [Hash] opts The options to use when opening an image.
|
41
|
-
# @option opts [Boolean] :
|
41
|
+
# @option opts [Boolean] :revalidate (Requires libvips > 8.15) When true, will force the underlying
|
42
|
+
# Vips library to reload the source file instead of using cached
|
43
|
+
# data from an earlier read. This is useful if the source
|
44
|
+
# file was recently recreated.
|
42
45
|
def self.image(src_path, opts = {})
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
Vips::Image.new_from_file(src_path)
|
46
|
+
if opts.empty?
|
47
|
+
Vips::Image.new_from_file(src_path)
|
48
|
+
else
|
49
|
+
Vips::Image.new_from_file(src_path, **opts)
|
50
|
+
end
|
49
51
|
end
|
50
52
|
|
51
53
|
# @param [String] src_path The local file path to the image.
|
52
54
|
# @param [Hash] opts The options to use when opening an image.
|
53
|
-
# @option opts [Boolean] :
|
55
|
+
# @option opts [Boolean] :revalidate (Requires libvips > 8.15) When true, will force the underlying
|
56
|
+
# Vips library to reload the source file instead of using cached
|
57
|
+
# data from an earlier read. This is useful if the source
|
58
|
+
# file was recently recreated.
|
54
59
|
def self.with_image(src_path, opts = {}, &block)
|
55
60
|
block.yield(image(src_path, opts))
|
56
61
|
end
|
57
|
-
|
58
|
-
# TODO: The clear_vips_cache_mem method can be removed once these two tickets are addressed:
|
59
|
-
# 1) https://github.com/libvips/ruby-vips/issues/360
|
60
|
-
# 2) https://github.com/libvips/libvips/pull/3370
|
61
|
-
def self.clear_vips_cache_mem
|
62
|
-
# store original max because we'll restore it later
|
63
|
-
original_max_value = vips_cache_get_max_mem
|
64
|
-
|
65
|
-
# Drop max mem to 0, which also internally triggers a trim operation that clears out old cache entries
|
66
|
-
vips_cache_set_max_mem(0)
|
67
|
-
|
68
|
-
# Restore original value to support future caching operations
|
69
|
-
vips_cache_set_max_mem(original_max_value)
|
70
|
-
end
|
71
62
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'imogen'
|
2
2
|
require 'tmpdir'
|
3
3
|
|
4
|
-
describe Imogen::
|
4
|
+
describe Imogen::Iiif, vips: true do
|
5
5
|
describe "#convert" do
|
6
6
|
let(:output_file) { Dir.tmpdir + '/test-imogen-convert.jpg' }
|
7
7
|
it "should successfully convert the image" do
|
@@ -16,3 +16,4 @@ describe Imogen::AutoCrop, vips: true do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'imogen'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
describe Imogen::Iiif::Tiles, vips: true do
|
5
|
+
let(:source_image) { fixture('sample.jpg').path } # sample.jpg is 1920w × 3125h
|
6
|
+
let(:tile_size) { 512 }
|
7
|
+
let(:output_dir) { Dir.tmpdir + '/tile-output-dir' }
|
8
|
+
let(:expected_files) do
|
9
|
+
[
|
10
|
+
'/0,0,512,512/512,/0/default.jpg',
|
11
|
+
'/0,512,512,512/512,/0/default.jpg',
|
12
|
+
'/0,1024,512,512/512,/0/default.jpg',
|
13
|
+
'/0,1536,512,512/512,/0/default.jpg',
|
14
|
+
'/0,2048,512,512/512,/0/default.jpg',
|
15
|
+
'/0,2560,512,512/512,/0/default.jpg',
|
16
|
+
'/0,3072,512,53/512,/0/default.jpg',
|
17
|
+
'/512,0,512,512/512,/0/default.jpg',
|
18
|
+
'/512,512,512,512/512,/0/default.jpg',
|
19
|
+
'/512,1024,512,512/512,/0/default.jpg',
|
20
|
+
'/512,1536,512,512/512,/0/default.jpg',
|
21
|
+
'/512,2048,512,512/512,/0/default.jpg',
|
22
|
+
'/512,2560,512,512/512,/0/default.jpg',
|
23
|
+
'/512,3072,512,53/512,/0/default.jpg',
|
24
|
+
'/1024,0,512,512/512,/0/default.jpg',
|
25
|
+
'/1024,512,512,512/512,/0/default.jpg',
|
26
|
+
'/1024,1024,512,512/512,/0/default.jpg',
|
27
|
+
'/1024,1536,512,512/512,/0/default.jpg',
|
28
|
+
'/1024,2048,512,512/512,/0/default.jpg',
|
29
|
+
'/1024,2560,512,512/512,/0/default.jpg',
|
30
|
+
'/1024,3072,512,53/512,/0/default.jpg',
|
31
|
+
'/1536,0,384,512/384,/0/default.jpg',
|
32
|
+
'/1536,512,384,512/384,/0/default.jpg',
|
33
|
+
'/1536,1024,384,512/384,/0/default.jpg',
|
34
|
+
'/1536,1536,384,512/384,/0/default.jpg',
|
35
|
+
'/1536,2048,384,512/384,/0/default.jpg',
|
36
|
+
'/1536,2560,384,512/384,/0/default.jpg',
|
37
|
+
'/1536,3072,384,53/384,/0/default.jpg',
|
38
|
+
'/0,0,1024,1024/512,/0/default.jpg',
|
39
|
+
'/0,1024,1024,1024/512,/0/default.jpg',
|
40
|
+
'/0,2048,1024,1024/512,/0/default.jpg',
|
41
|
+
'/0,3072,1024,53/512,/0/default.jpg',
|
42
|
+
'/1024,0,896,1024/448,/0/default.jpg',
|
43
|
+
'/1024,1024,896,1024/448,/0/default.jpg',
|
44
|
+
'/1024,2048,896,1024/448,/0/default.jpg',
|
45
|
+
'/1024,3072,896,53/448,/0/default.jpg',
|
46
|
+
'/0,0,1920,2048/480,/0/default.jpg',
|
47
|
+
'/0,2048,1920,1077/480,/0/default.jpg',
|
48
|
+
'/full/240,/0/default.jpg',
|
49
|
+
'/full/120,/0/default.jpg',
|
50
|
+
].map { |partial_path| File.join(output_dir, partial_path) }
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#for" do
|
54
|
+
it "should successfully generate the expected tile files" do
|
55
|
+
Imogen.with_image(source_image) do |image|
|
56
|
+
Imogen::Iiif::Tiles.for(image, output_dir, :jpeg, tile_size) do |img, suggested_tile_dest_path, format, opts|
|
57
|
+
FileUtils.mkdir_p(File.dirname(suggested_tile_dest_path))
|
58
|
+
Imogen::Iiif.convert(img, suggested_tile_dest_path, format, opts)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
generated_files = Dir["#{output_dir}/**/*.jpg"]
|
62
|
+
|
63
|
+
# Check for missing files and extra unexpected files
|
64
|
+
missing_files = expected_files.sort - generated_files.sort
|
65
|
+
extra_unexepcted_files = generated_files.sort - expected_files.sort
|
66
|
+
expect(missing_files).to eq([])
|
67
|
+
expect(extra_unexepcted_files).to eq([])
|
68
|
+
ensure
|
69
|
+
FileUtils.rm_rf(output_dir) if File.exist?(output_dir)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "#generate_vips_dzsave_tiles" do
|
74
|
+
pending "should successfully generate the expected tile files" do
|
75
|
+
Imogen.with_image(source_image) do |image|
|
76
|
+
Imogen::Iiif::Tiles.generate_with_vips_dzsave(image, output_dir, format: 'jpg', tile_size: tile_size)
|
77
|
+
end
|
78
|
+
generated_files = Dir["#{output_dir}/**/*.jpg"]
|
79
|
+
|
80
|
+
# Check for missing files and extra unexpected files
|
81
|
+
missing_files = expected_files.sort - generated_files.sort
|
82
|
+
extra_unexepcted_files = generated_files.sort - expected_files.sort
|
83
|
+
# TODO: Remove this `if` statement and `warn` method call once the generate_vips_dzsave_tiles implementation works properly.
|
84
|
+
if missing_files.length.positive? || extra_unexepcted_files.length.positive?
|
85
|
+
warn "generate_with_vips_dzsave test:\nmissing_files: #{missing_files.length}, extra_unexepcted_files: #{extra_unexepcted_files.length}"
|
86
|
+
end
|
87
|
+
expect(missing_files).to eq([])
|
88
|
+
expect(extra_unexepcted_files).to eq([])
|
89
|
+
ensure
|
90
|
+
FileUtils.rm_rf(output_dir) if File.exist?(output_dir)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
@@ -3,21 +3,19 @@ require 'tmpdir'
|
|
3
3
|
|
4
4
|
describe Imogen, vips: true do
|
5
5
|
describe ".with_image" do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
let(:source_path) { fixture('sample.jpg').path }
|
7
|
+
let(:revalidate) { true }
|
8
|
+
|
9
|
+
it 'calls Vips::Image.new_from_file in the expected way when no opts are passed' do
|
10
|
+
expect(Vips::Image).to receive(:new_from_file).with(source_path)
|
11
|
+
Imogen.with_image(source_path) do |img|
|
10
12
|
# don't need to do anything with the image for this test
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
|
-
it '
|
15
|
-
expect(Vips::Image).to receive(:new_from_file).
|
16
|
-
|
17
|
-
Imogen.with_image(fixture('sample.jpg').path, nocache: false) do |img|
|
18
|
-
# don't need to do anything with the image for this test
|
19
|
-
end
|
20
|
-
Imogen.with_image(fixture('sample.jpg').path) do |img|
|
16
|
+
it 'passes the revalidate option to the underlying Vips::Image.new_from_file method' do
|
17
|
+
expect(Vips::Image).to receive(:new_from_file).with(source_path, revalidate: revalidate)
|
18
|
+
Imogen.with_image(source_path, revalidate: revalidate) do |img|
|
21
19
|
# don't need to do anything with the image for this test
|
22
20
|
end
|
23
21
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'imogen/iiif'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
describe Imogen::Iiif::Tiles, type: :unit do
|
4
|
+
describe 'OpenSeadragon Tile expectations' do
|
5
|
+
let(:tile_size) { 512 }
|
6
|
+
let(:portrait_test_image) { ImageStub.new(1920, 3125) }
|
7
|
+
let(:osd_portrait_expected_tiles) do
|
8
|
+
[
|
9
|
+
'/0,0,1024,1024/512,/0/default.jpg',
|
10
|
+
'/0,0,1920,2048/480,/0/default.jpg',
|
11
|
+
'/0,0,512,512/512,/0/default.jpg',
|
12
|
+
'/0,1024,1024,1024/512,/0/default.jpg',
|
13
|
+
'/0,1024,512,512/512,/0/default.jpg',
|
14
|
+
'/0,1536,512,512/512,/0/default.jpg',
|
15
|
+
'/0,2048,1024,1024/512,/0/default.jpg',
|
16
|
+
'/0,2048,1920,1077/480,/0/default.jpg',
|
17
|
+
'/0,2048,512,512/512,/0/default.jpg',
|
18
|
+
'/0,2560,512,512/512,/0/default.jpg',
|
19
|
+
'/0,3072,1024,53/512,/0/default.jpg',
|
20
|
+
'/0,3072,512,53/512,/0/default.jpg',
|
21
|
+
'/0,512,512,512/512,/0/default.jpg',
|
22
|
+
'/1024,0,512,512/512,/0/default.jpg',
|
23
|
+
'/1024,0,896,1024/448,/0/default.jpg',
|
24
|
+
'/1024,1024,512,512/512,/0/default.jpg',
|
25
|
+
'/1024,1024,896,1024/448,/0/default.jpg',
|
26
|
+
'/1024,1536,512,512/512,/0/default.jpg',
|
27
|
+
'/1024,2048,512,512/512,/0/default.jpg',
|
28
|
+
'/1024,2048,896,1024/448,/0/default.jpg',
|
29
|
+
'/1024,2560,512,512/512,/0/default.jpg',
|
30
|
+
'/1024,3072,512,53/512,/0/default.jpg',
|
31
|
+
'/1024,3072,896,53/448,/0/default.jpg',
|
32
|
+
'/1024,512,512,512/512,/0/default.jpg',
|
33
|
+
'/1536,0,384,512/384,/0/default.jpg',
|
34
|
+
'/1536,1024,384,512/384,/0/default.jpg',
|
35
|
+
'/1536,1536,384,512/384,/0/default.jpg',
|
36
|
+
'/1536,2048,384,512/384,/0/default.jpg',
|
37
|
+
'/1536,2560,384,512/384,/0/default.jpg',
|
38
|
+
'/1536,3072,384,53/384,/0/default.jpg',
|
39
|
+
'/1536,512,384,512/384,/0/default.jpg',
|
40
|
+
'/512,0,512,512/512,/0/default.jpg',
|
41
|
+
'/512,1024,512,512/512,/0/default.jpg',
|
42
|
+
'/512,1536,512,512/512,/0/default.jpg',
|
43
|
+
'/512,2048,512,512/512,/0/default.jpg',
|
44
|
+
'/512,2560,512,512/512,/0/default.jpg',
|
45
|
+
'/512,3072,512,53/512,/0/default.jpg',
|
46
|
+
'/512,512,512,512/512,/0/default.jpg',
|
47
|
+
'/full/120,/0/default.jpg',
|
48
|
+
'/full/240,/0/default.jpg',
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
let(:landscape_test_image) { ImageStub.new(1920, 1213) }
|
53
|
+
let(:osd_landscape_expected_tiles) do
|
54
|
+
[
|
55
|
+
'/0,0,1024,1024/512,/0/default.jpg',
|
56
|
+
'/0,0,512,512/512,/0/default.jpg',
|
57
|
+
'/0,1024,1024,189/512,/0/default.jpg',
|
58
|
+
'/0,1024,512,189/512,/0/default.jpg',
|
59
|
+
'/0,512,512,512/512,/0/default.jpg',
|
60
|
+
'/1024,0,512,512/512,/0/default.jpg',
|
61
|
+
'/1024,0,896,1024/448,/0/default.jpg',
|
62
|
+
'/1024,1024,512,189/512,/0/default.jpg',
|
63
|
+
'/1024,1024,896,189/448,/0/default.jpg',
|
64
|
+
'/1024,512,512,512/512,/0/default.jpg',
|
65
|
+
'/1536,0,384,512/384,/0/default.jpg',
|
66
|
+
'/1536,1024,384,189/384,/0/default.jpg',
|
67
|
+
'/1536,512,384,512/384,/0/default.jpg',
|
68
|
+
'/512,0,512,512/512,/0/default.jpg',
|
69
|
+
'/512,1024,512,189/512,/0/default.jpg',
|
70
|
+
'/512,512,512,512/512,/0/default.jpg',
|
71
|
+
'/full/240,/0/default.jpg',
|
72
|
+
'/full/480,/0/default.jpg'
|
73
|
+
]
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#for' do
|
77
|
+
it 'should produce the expected tiles for a sample dimension portrait orientation image' do
|
78
|
+
actual = []
|
79
|
+
expected = osd_portrait_expected_tiles
|
80
|
+
described_class.for(portrait_test_image, '', :jpeg, tile_size) do |img, dest_path, format, opts|
|
81
|
+
actual << dest_path
|
82
|
+
end
|
83
|
+
|
84
|
+
# Check for missing files and extra unexpected files
|
85
|
+
missing_files = expected.sort - actual.sort
|
86
|
+
extra_unexepcted_files = actual.sort - expected.sort
|
87
|
+
expect(missing_files).to eq([])
|
88
|
+
expect(extra_unexepcted_files).to eq([])
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should produce the expected tiles for a sample dimension landscape orientation image' do
|
92
|
+
actual = []
|
93
|
+
expected = osd_landscape_expected_tiles
|
94
|
+
described_class.for(landscape_test_image, '', :jpeg, tile_size) do |img, dest_path, format, opts|
|
95
|
+
actual << dest_path
|
96
|
+
end
|
97
|
+
|
98
|
+
# Check for missing files and extra unexpected files
|
99
|
+
missing_files = expected.sort - actual.sort
|
100
|
+
extra_unexepcted_files = actual.sort - expected.sort
|
101
|
+
expect(missing_files).to eq([])
|
102
|
+
expect(extra_unexepcted_files).to eq([])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -2,77 +2,97 @@ require 'imogen/iiif'
|
|
2
2
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
3
|
describe Imogen::Iiif::Tiles, type: :unit do
|
4
4
|
before(:all) do
|
5
|
-
@test_image = ImageStub.new(175,131)
|
5
|
+
@test_image = ImageStub.new(175, 131)
|
6
6
|
end
|
7
|
-
let(:
|
7
|
+
let(:tile512) {
|
8
|
+
[
|
9
|
+
'/full/full/0/default.jpg',
|
10
|
+
'/full/175,/0/default.jpg'
|
11
|
+
]
|
12
|
+
}
|
13
|
+
let(:tile256) {
|
14
|
+
[
|
15
|
+
'/full/full/0/default.jpg',
|
16
|
+
'/full/175,/0/default.jpg',
|
17
|
+
'/full/88,/0/default.jpg'
|
18
|
+
]
|
19
|
+
}
|
8
20
|
let(:tile128) {
|
9
21
|
[
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
22
|
+
'/0,0,128,128/128,/0/default.jpg',
|
23
|
+
'/0,128,128,3/128,/0/default.jpg',
|
24
|
+
'/128,0,47,128/47,/0/default.jpg',
|
25
|
+
'/128,128,47,3/47,/0/default.jpg',
|
26
|
+
'/full/88,/0/default.jpg',
|
27
|
+
'/full/44,/0/default.jpg'
|
14
28
|
]
|
15
29
|
}
|
16
30
|
let(:tile64) {
|
17
31
|
[
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
32
|
+
'/0,0,64,64/64,/0/default.jpg',
|
33
|
+
'/0,64,64,64/64,/0/default.jpg',
|
34
|
+
'/0,128,64,3/64,/0/default.jpg',
|
35
|
+
'/64,0,64,64/64,/0/default.jpg',
|
36
|
+
'/64,64,64,64/64,/0/default.jpg',
|
37
|
+
'/64,128,64,3/64,/0/default.jpg',
|
38
|
+
'/128,0,47,64/47,/0/default.jpg',
|
39
|
+
'/128,64,47,64/47,/0/default.jpg',
|
40
|
+
'/128,128,47,3/47,/0/default.jpg',
|
41
|
+
'/0,0,128,128/64,/0/default.jpg',
|
42
|
+
'/0,128,128,3/64,/0/default.jpg',
|
43
|
+
'/128,0,47,128/24,/0/default.jpg',
|
44
|
+
'/128,128,47,3/24,/0/default.jpg',
|
45
|
+
'/full/44,/0/default.jpg',
|
46
|
+
'/full/22,/0/default.jpg'
|
27
47
|
]
|
28
48
|
}
|
29
|
-
describe '#
|
30
|
-
it 'should calculate for
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
49
|
+
describe '#scale_factors_for' do
|
50
|
+
it 'should calculate the expected scale factors for the given image width, height, and tile_size' do
|
51
|
+
{
|
52
|
+
32 => [1, 2, 4, 8, 16],
|
53
|
+
64 => [1, 2, 4, 8],
|
54
|
+
128 => [1, 2, 4],
|
55
|
+
256 => [1, 2],
|
56
|
+
512 => [1]
|
57
|
+
}.each do |tile_size, expected_scale_factors|
|
58
|
+
expect(
|
59
|
+
described_class.scale_factors_for(@test_image.width, @test_image.height, tile_size)
|
60
|
+
).to eq(expected_scale_factors)
|
36
61
|
end
|
37
62
|
end
|
38
63
|
end
|
39
64
|
describe '#for' do
|
40
|
-
it 'should produce
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
65
|
+
it 'should produce the expected tiles for different tile sizes' do
|
66
|
+
{
|
67
|
+
512 => tile512,
|
68
|
+
256 => tile256,
|
69
|
+
128 => tile128,
|
70
|
+
64 => tile64
|
71
|
+
}.each do |tile_size, expected_tiles|
|
72
|
+
actual = []
|
73
|
+
described_class.for(@test_image, '', :jpeg, tile_size) do |img, dest_path, format, opts|
|
74
|
+
actual << dest_path
|
75
|
+
end
|
76
|
+
expect(actual).to eql(expected_tiles)
|
45
77
|
end
|
46
|
-
expect(actual).to eql(expected)
|
47
78
|
end
|
48
|
-
it 'should produce
|
49
|
-
expected =
|
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
|
79
|
+
it 'should produce png when requested' do
|
80
|
+
expected = tile256.collect {|x| x.sub(/jpg$/, 'png')}
|
60
81
|
expected.uniq!
|
61
82
|
actual = []
|
62
|
-
|
83
|
+
described_class.for(@test_image, '', :png, 256) do |img, dest_path, format, opts|
|
63
84
|
actual << dest_path
|
64
85
|
end
|
65
|
-
actual.
|
66
|
-
expect(actual.sort).to eql(expected.sort)
|
86
|
+
expect(actual).to eql(expected)
|
67
87
|
end
|
68
|
-
it 'should produce
|
69
|
-
expected =
|
88
|
+
it 'should produce the expected quality when requested' do
|
89
|
+
expected = tile256.collect {|x| x.sub(/default.jpg$/, 'color.jpg')}
|
70
90
|
expected.uniq!
|
71
91
|
actual = []
|
72
|
-
|
92
|
+
described_class.for(@test_image, '', :jpg, 256, 'color') do |img, dest_path, format, opts|
|
73
93
|
actual << dest_path
|
74
94
|
end
|
75
95
|
expect(actual).to eql(expected)
|
76
96
|
end
|
77
97
|
end
|
78
|
-
end
|
98
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: imogen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Armintor
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-09-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ruby-vips
|
@@ -73,6 +73,7 @@ files:
|
|
73
73
|
- spec/fixtures/files/sample.jpg
|
74
74
|
- spec/integration/imogen_autocrop_spec.rb
|
75
75
|
- spec/integration/imogen_iiif_spec.rb
|
76
|
+
- spec/integration/imogen_iiif_tiles_spec.rb
|
76
77
|
- spec/integration/imogen_spec.rb
|
77
78
|
- spec/integration/transparent_image_conversion_spec.rb
|
78
79
|
- spec/spec_helper.rb
|
@@ -80,6 +81,7 @@ files:
|
|
80
81
|
- spec/unit/imogen_iiif_region_spec.rb
|
81
82
|
- spec/unit/imogen_iiif_rotate_spec.rb
|
82
83
|
- spec/unit/imogen_iiif_size_spec.rb
|
84
|
+
- spec/unit/imogen_iiif_tiles_openseadragon_expectations_spec.rb
|
83
85
|
- spec/unit/imogen_iiif_tiles_spec.rb
|
84
86
|
homepage: https://github.com/cul/imogen
|
85
87
|
licenses: []
|
@@ -99,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
101
|
- !ruby/object:Gem::Version
|
100
102
|
version: '0'
|
101
103
|
requirements: []
|
102
|
-
rubygems_version: 3.
|
104
|
+
rubygems_version: 3.4.10
|
103
105
|
signing_key:
|
104
106
|
specification_version: 4
|
105
107
|
summary: IIIF image derivative generation helpers for Vips
|