imogen 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbcea943f658496d2794d4def050cc0ec3bac40ce2912586ab62e1152656fe50
4
- data.tar.gz: 1b69768da2f5229e1cf18025030d05eee77f81e8c185f40f60ede9bc287bc346
3
+ metadata.gz: 032e49d9a8602410ab24b57efc8bd3de1cd523ad41e217bd76affcca385a08aa
4
+ data.tar.gz: 2ae67a08b37695662dd21af0eedc7c7449423fde11ca20cd02c2f66e00ade5f4
5
5
  SHA512:
6
- metadata.gz: 6dcee0b0c7e0eb873f37487d07aac005e2cf3f6cd532c7e1094b2a3d360d2f51642bcd48378f32b14270fc7af1b722ada6f12e5c7bcfd5c96b236cc1f60c7b83
7
- data.tar.gz: '094cbbd705e9d2bc7a88245e254c6de70788a4d6f5f23c814ce1d52bf6ec55c186d84948fb12233605490034eff119aa10bcf458219d4d8bf44d4e16e760e852'
6
+ metadata.gz: 9a5499840827444cea022ef95ca205f52131084938a6417441eb73898961b726b02229d6f2e14964a77bf4fa24a537ae7b5c582fec1d7bcaf72f556e789dbd23
7
+ data.tar.gz: 6f53cf3ee9a63cd4906f2f09ede862f31440e573ea0d7033ce37c50d0d661d733dc7cb355e2a137e94f6a3eeb5e91904d2c37c7059039d145722a0b976b656cd
@@ -2,37 +2,160 @@ require 'imogen/zoomable'
2
2
  module Imogen
3
3
  module Iiif
4
4
  module Tiles
5
- def self.scale_factor_for(*dims)
6
- Imogen::Zoomable.levels_for(*dims)**2
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
- 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
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
- y, row = 0, 0
57
+ row = 0
58
+ y = 0
21
59
  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))
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
- y += region_size
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
@@ -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] :nocache When true, will clear the Vips cache before opening the image.
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
- # TODO: Change to `new_from_file(src_path, {nocache: nocache})`, or something similar,
44
- # when these two GitHub issues are addressed:
45
- # 1) https://github.com/libvips/ruby-vips/issues/360
46
- # 2) https://github.com/libvips/libvips/pull/3370
47
- clear_vips_cache_mem if opts[:nocache] == true
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] :nocache When true, will clear the Vips cache before opening the image.
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::AutoCrop, vips: true do
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
- it 'should call clear_vips_cache_mem when nocache option is provided' do
7
- expect(Vips::Image).to receive(:new_from_file)
8
- expect(Imogen).to receive(:clear_vips_cache_mem)
9
- Imogen.with_image(fixture('sample.jpg').path, nocache: true) do |img|
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 'should not call clear_vips_cache_mem when nocache option is not provided, or is false' do
15
- expect(Vips::Image).to receive(:new_from_file).twice
16
- expect(Imogen).not_to receive(:clear_vips_cache_mem)
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(:fullSize) { ["/0,0,175,131/full/0/native.jpg"]}
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
- "/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"
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
- "/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",
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 '#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)
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 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
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 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
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
- subject.for(@test_image,'',:jpeg,64) do |img,dest_path,format,opts|
83
+ described_class.for(@test_image, '', :png, 256) do |img, dest_path, format, opts|
63
84
  actual << dest_path
64
85
  end
65
- actual.uniq!
66
- expect(actual.sort).to eql(expected.sort)
86
+ expect(actual).to eql(expected)
67
87
  end
68
- it 'should produce png when requested' do
69
- expected = fullSize.collect {|x| x.sub(/jpg$/,'png')}
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
- subject.for(@test_image,'',:png,256) do |img,dest_path,format,opts|
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.3.2
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: 2023-09-21 00:00:00.000000000 Z
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.2.32
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