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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea96ea6896a683acb44ea5e961a1f0b6b5584b2b12046b9fcc02f8a0de1094fa
4
- data.tar.gz: 52d98cea18de1c25645285f1e5a5ee6c39296ce2327215a972138a0a32fefca9
3
+ metadata.gz: 032e49d9a8602410ab24b57efc8bd3de1cd523ad41e217bd76affcca385a08aa
4
+ data.tar.gz: 2ae67a08b37695662dd21af0eedc7c7449423fde11ca20cd02c2f66e00ade5f4
5
5
  SHA512:
6
- metadata.gz: 66026e6285cb28981400f001c6ecc78ecf2e2cfe62f664d7a1fd911b37ce298c2ca49fb9643ade651c2d21815a5b475a557bd4b2670c42e18d7b13af9f65ce26
7
- data.tar.gz: ba2f95151b23693d2c16887c59baa5be9ee2554c9b875d6d9def3e37858e2ffee36b30e996d3e98eb0124fe662acb33dd83979e231a63400907a6543ae58510d
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
@@ -1,6 +1,6 @@
1
1
  module Imogen
2
2
  module Iiif
3
- class BadRequest < Exception; end
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] :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.1
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-03-12 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.3.26
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