riiif 2.1.0 → 2.8.1

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/ruby.yml +39 -0
  4. data/.rubocop_todo.yml +115 -16
  5. data/Gemfile +2 -12
  6. data/README.md +29 -9
  7. data/app/controllers/riiif/images_controller.rb +1 -1
  8. data/app/extractors/riiif/abstract_info_extractor.rb +12 -0
  9. data/app/{services → extractors}/riiif/image_magick_info_extractor.rb +2 -7
  10. data/app/extractors/riiif/vips_info_extractor.rb +22 -0
  11. data/app/models/riiif/file.rb +11 -24
  12. data/app/models/riiif/image.rb +0 -3
  13. data/app/resolvers/riiif/abstract_file_system_resolver.rb +1 -11
  14. data/app/resolvers/riiif/akubra_system_file_resolver.rb +1 -0
  15. data/app/resolvers/riiif/file_system_file_resolver.rb +1 -1
  16. data/app/resolvers/riiif/http_file_resolver.rb +11 -3
  17. data/app/services/riiif/crop.rb +40 -0
  18. data/app/services/riiif/imagemagick_command_factory.rb +1 -1
  19. data/app/services/riiif/vips_resize.rb +47 -0
  20. data/app/transformers/riiif/vips_transformer.rb +103 -0
  21. data/docs/vips_comparison.md +257 -0
  22. data/lib/riiif/engine.rb +14 -0
  23. data/lib/riiif/version.rb +1 -1
  24. data/lib/riiif.rb +14 -0
  25. data/riiif.gemspec +9 -7
  26. data/spec/controllers/riiif/images_controller_spec.rb +8 -8
  27. data/spec/extractors/riiif/image_magick_info_extractor_spec.rb +31 -0
  28. data/spec/extractors/riiif/vips_info_extractor_spec.rb +76 -0
  29. data/spec/fixtures/test.jpg +0 -0
  30. data/spec/fixtures/test.png +0 -0
  31. data/spec/fixtures/test.tif +0 -0
  32. data/spec/models/riiif/file_spec.rb +35 -0
  33. data/spec/models/riiif/file_system_file_resolver_spec.rb +7 -0
  34. data/spec/models/riiif/http_file_resolver_spec.rb +1 -1
  35. data/spec/models/riiif/image_spec.rb +27 -21
  36. data/spec/spec_helper.rb +0 -6
  37. data/spec/test_app_templates/lib/generators/test_app_generator.rb +6 -1
  38. data/spec/transformers/riiif/kakadu_transformer_spec.rb +8 -8
  39. data/spec/transformers/riiif/vips_transformer_spec.rb +271 -0
  40. metadata +49 -19
  41. data/.travis.yml +0 -11
@@ -0,0 +1,47 @@
1
+ module Riiif
2
+ # Represents a resize operation
3
+ class VipsResize
4
+ def initialize(size, image)
5
+ @size = size
6
+ @image = image
7
+ end
8
+
9
+ attr_reader :size, :image
10
+
11
+ # @return the parameters that vips will use to resize the image. This can be
12
+ # 1. A [Float] representing the scale factor, passed to Vips::Image#resize
13
+ # 2. An [Array], where the 1st elem is an Integer and the 2nd is a
14
+ # Hash of options, passed to Vips::Image#thumbnail
15
+ # 3. [NilClass] when image should not be resized at all
16
+ def to_vips
17
+ case size
18
+ when IIIF::Image::Size::Percent
19
+ size.percentage
20
+ when IIIF::Image::Size::Width
21
+ resize_ratio(:width, image)
22
+ when IIIF::Image::Size::Height
23
+ resize_ratio(:height, image)
24
+ when IIIF::Image::Size::Absolute
25
+ [size.width, { height: size.height, size: :force }]
26
+ when IIIF::Image::Size::BestFit
27
+ [size.width, { height: size.height }]
28
+ when IIIF::Image::Size::Max, IIIF::Image::Size::Full
29
+ nil
30
+ else
31
+ raise "unknown size #{size.class}"
32
+ end
33
+ end
34
+
35
+ # @param [Symbol] - which side of the image to calculate, either :width or :height
36
+ # @return [Float] - the scale or percentage to resize the image by; passed to Vips::Image#resize
37
+ def resize_ratio(side, image)
38
+ length = image.send(side)
39
+ target_length = size.send(side)
40
+ if target_length < length
41
+ target_length / length.to_f # Size down
42
+ else
43
+ length / target_length.to_f # Size up
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,103 @@
1
+ # Use ruby-vips to execute image transformations (via ffi gem) instead of
2
+ # using the vips CLI. Since vips CLI commands can't be chained without creating
3
+ # temp files after each operation, using the CLI would decrease performance.
4
+ # See 'Chaining operations': https://www.libvips.org/API/current/using-cli.html
5
+ require 'ruby-vips' if Riiif::Engine.config.use_vips
6
+
7
+ module Riiif
8
+ class VipsTransformer < AbstractTransformer
9
+ include ActiveSupport::Benchmarkable
10
+ delegate :logger, to: :Rails
11
+
12
+ # @param path [String] The path of the source image file
13
+ # @param image_info [ImageInformation] information about the source
14
+ # @param [IIIF::Image::Transformation] transformation
15
+ def initialize(path, image_info, transformation, compression: 85, subsample: true, strip_metadata: true)
16
+ super(path, image_info, transformation)
17
+ @image = ::Vips::Image.new_from_file(path.to_s)
18
+ @compression = compression
19
+ @subsample = subsample
20
+ @strip_metadata = strip_metadata
21
+ end
22
+
23
+ attr_reader :image, :path, :compression, :subsample, :strip_metadata
24
+
25
+ # @return [String] all the image data
26
+ def transform
27
+ benchmark("Riiif transformed image using vips") do
28
+ transform_image.write_to_buffer(".#{format}#{format_options}")
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # Chain every method in the array together and apply it to the image
35
+ # @return [Vips::Image] - the image after all transformations
36
+ def transform_image
37
+ result = [crop, resize, rotate, colourspace].reduce(image) do |image, array|
38
+ method, options = array
39
+ # Options are blank when transformation is not required (e.g. when requesting full size)
40
+ next image if options.blank?
41
+
42
+ case method
43
+ when :resize
44
+ image.send(method, VipsResize.new(transformation.size, image).to_vips)
45
+ when :thumbnail_image
46
+ # .thumbnail_image needs a positional argument (width) and keyword args (options)
47
+ # https://www.rubydoc.info/gems/ruby-vips/Vips/Image#thumbnail_image-instance_method
48
+ image.send(method, options.first, **options.last)
49
+ when :crop
50
+ # .crop needs positional arguments
51
+ image.send(method, *options)
52
+ else # :rotate or :colourspace
53
+ image.send(method, options)
54
+ end
55
+ end
56
+ # If result should be bitonal, set a value threshold
57
+ # https://github.com/libvips/libvips/issues/1840
58
+ transformation.quality == 'bitonal' ? (result > 200) : result
59
+ end
60
+
61
+ def format
62
+ # In cases where the input file has an alpha_channel but the transformation
63
+ # format is 'jpg', change to 'png' as jpeg does not support alpha channels
64
+ image.has_alpha? && transformation.format == 'jpg' ? 'png' : transformation.format
65
+ end
66
+
67
+ def format_options
68
+ format_string = [compression,
69
+ ("optimize-coding" if format == 'jpg'),
70
+ ("strip" if strip_metadata),
71
+ ("no-subsample" unless subsample)].select(&:present?).join(',')
72
+
73
+ "[Q=#{format_string}]"
74
+ end
75
+
76
+ def resize
77
+ case transformation.size
78
+ when IIIF::Image::Size::Percent, IIIF::Image::Size::Width, IIIF::Image::Size::Height
79
+ [:resize, transformation.size]
80
+ else # IIIF::Image::Size::Absolute, IIIF::Image::Size::BestFit
81
+ [:thumbnail_image, VipsResize.new(transformation.size, image).to_vips]
82
+ end
83
+ end
84
+
85
+ def crop
86
+ [:crop, Crop.new(transformation.region, image_info).to_vips]
87
+ end
88
+
89
+ def rotate
90
+ angle = transformation.rotation.zero? ? nil : transformation.rotation
91
+ [:rotate, angle]
92
+ end
93
+
94
+ def colourspace
95
+ case transformation.quality
96
+ when 'gray', 'bitonal'
97
+ [:colourspace, :b_w]
98
+ else
99
+ [:colourspace, nil]
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,257 @@
1
+ # Benchmarks comparing Vips, Imagemagick, and Graphicsmagick
2
+
3
+ The following benchmarks were run using ApacheBench v. 2.3 on Ubuntu / Windows Subsystem for Linux (WSL) on a 32 GB (RAM) laptop.
4
+
5
+ To generate the tables below, a simple resize `curl` command (more details below) was run 50 times in a row and the median response time was calculated.
6
+
7
+ ## Testing with a JPEG
8
+
9
+ For a 4264 x 3282 jpeg image (26.8 MB)
10
+
11
+ | Software Used | Median processing time (ms) | Mean processing time (ms) |
12
+ | ---------------|-----------------------------|---------------------------|
13
+ | Imagemagick | 753 | 753 |
14
+ | Graphicsmagick | 662 | 660 |
15
+ | Vips | 79 | 78 |
16
+
17
+ ## Testing with a TIFF
18
+
19
+ For a 7800 x 5865 tif image (7.11 MB)
20
+
21
+ | Software Used | Median processing time (ms) | Mean processing time (ms) |
22
+ | ---------------|-----------------------------|---------------------------|
23
+ | Imagemagick | 1091 | 1089 |
24
+ | Graphicsmagick | 800 | 796 |
25
+ | Vips | 130 | 139 |
26
+
27
+ ## More Resources & Discussion
28
+
29
+ Those interested in more comprehensive benchmarking with Ruby may be interested in the [vips-benchmarks](https://github.com/jcupitt/vips-benchmarks?tab=readme-ov-file) code repository, which also tests memory usage.
30
+
31
+ Glen Robson, Stefano Cossu, Ruven Pillay, and Michael D. Smith have written an [excellent article comparing the speed of different image processing tools and formats in a IIIF context](https://journal.code4lib.org/articles/17596). They write, "The testing clearly shows that tiled multi-resolution pyramid TIFF is the fastest format for IIIF, but it comes at a cost of significantly more storage space compared to both HTJ2K [([High Throughput JPEG2000](https://jpeg.org/jpeg2000/htj2k.html))] and JP2." The latter two standards are used by [Kakadu](https://kakadusoftware.com/), a proprietary image toolkit that is commonly used for IIIF servers.
32
+
33
+ Based on their results, institutions/organizations that use large TIFFs as the base image for IIIF derivatives will likely see the best performance using vips. Conversely, institutions/organizations that use JP2 images will get the best performance using Kakadu HTJ2K.
34
+
35
+ ## Command and Detailed Results for JPGs
36
+
37
+ Command: `ab -n 50 'http://localhost:3000/images/irises/full/!500,500/0/default.jpg'`
38
+
39
+ ### Using imagemagick
40
+
41
+ ```
42
+ Document Path: /images/irises/full/!500,500/0/default.jpg
43
+ Document Length: 79018 bytes
44
+
45
+ Concurrency Level: 1
46
+ Time taken for tests: 37.655 seconds
47
+ Complete requests: 50
48
+ Failed requests: 0
49
+ Total transferred: 3995500 bytes
50
+ HTML transferred: 3950900 bytes
51
+ Requests per second: 1.33 [#/sec] (mean)
52
+ Time per request: 753.097 [ms] (mean)
53
+ Time per request: 753.097 [ms] (mean, across all concurrent requests)
54
+ Transfer rate: 103.62 [Kbytes/sec] received
55
+
56
+ Connection Times (ms)
57
+ min mean[+/-sd] median max
58
+ Connect: 0 0 0.0 0 0
59
+ Processing: 701 753 28.6 753 813
60
+ Waiting: 701 753 28.6 753 813
61
+ Total: 701 753 28.6 753 813
62
+
63
+ Percentage of the requests served within a certain time (ms)
64
+ 50% 753
65
+ 66% 768
66
+ 75% 774
67
+ 80% 778
68
+ 90% 794
69
+ 95% 810
70
+ 98% 813
71
+ 99% 813
72
+ 100% 813 (longest request)
73
+ ```
74
+
75
+ ### Using graphicsmagick
76
+
77
+ ```
78
+ Document Path: /images/irises/full/!500,500/0/default.jpg
79
+ Document Length: 78992 bytes
80
+
81
+ Concurrency Level: 1
82
+ Time taken for tests: 33.131 seconds
83
+ Complete requests: 50
84
+ Failed requests: 0
85
+ Total transferred: 3994173 bytes
86
+ HTML transferred: 3949600 bytes
87
+ Requests per second: 1.51 [#/sec] (mean)
88
+ Time per request: 662.629 [ms] (mean)
89
+ Time per request: 662.629 [ms] (mean, across all concurrent requests)
90
+ Transfer rate: 117.73 [Kbytes/sec] received
91
+
92
+ Connection Times (ms)
93
+ min mean[+/-sd] median max
94
+ Connect: 0 0 0.0 0 0
95
+ Processing: 539 662 64.9 660 847
96
+ Waiting: 539 662 64.9 660 847
97
+ Total: 539 663 65.0 660 848
98
+
99
+ Percentage of the requests served within a certain time (ms)
100
+ 50% 660
101
+ 66% 685
102
+ 75% 703
103
+ 80% 720
104
+ 90% 739
105
+ 95% 775
106
+ 98% 848
107
+ 99% 848
108
+ 100% 848 (longest request)
109
+ ```
110
+
111
+ ### Using libvips
112
+
113
+ ```
114
+ Document Path: /images/irises/full/!500,500/0/default.jpg
115
+ Document Length: 77647 bytes
116
+
117
+ Concurrency Level: 1
118
+ Time taken for tests: 3.920 seconds
119
+ Complete requests: 50
120
+ Failed requests: 0
121
+ Total transferred: 3926860 bytes
122
+ HTML transferred: 3882350 bytes
123
+ Requests per second: 12.75 [#/sec] (mean)
124
+ Time per request: 78.409 [ms] (mean)
125
+ Time per request: 78.409 [ms] (mean, across all concurrent requests)
126
+ Transfer rate: 978.16 [Kbytes/sec] received
127
+
128
+ Connection Times (ms)
129
+ min mean[+/-sd] median max
130
+ Connect: 0 0 0.0 0 0
131
+ Processing: 67 78 5.5 79 90
132
+ Waiting: 67 78 5.5 79 90
133
+ Total: 67 78 5.5 79 90
134
+
135
+ Percentage of the requests served within a certain time (ms)
136
+ 50% 79
137
+ 66% 81
138
+ 75% 81
139
+ 80% 82
140
+ 90% 86
141
+ 95% 88
142
+ 98% 90
143
+ 99% 90
144
+ 100% 90 (longest request)
145
+ ```
146
+
147
+ ## Command and Detailed Results for TIFFs
148
+
149
+ Command: `ab -n 50 'http://localhost:3000/images/big/full/!500,500/0/default.jpg'`
150
+
151
+ ### Using imagemagick
152
+
153
+ ```
154
+ Document Path: /images/big/full/!500,500/0/default.jpg
155
+ Document Length: 82826 bytes
156
+
157
+ Concurrency Level: 1
158
+ Time taken for tests: 54.537 seconds
159
+ Complete requests: 50
160
+ Failed requests: 0
161
+ Total transferred: 4185950 bytes
162
+ HTML transferred: 4141300 bytes
163
+ Requests per second: 0.92 [#/sec] (mean)
164
+ Time per request: 1090.745 [ms] (mean)
165
+ Time per request: 1090.745 [ms] (mean, across all concurrent requests)
166
+ Transfer rate: 74.96 [Kbytes/sec] received
167
+
168
+ Connection Times (ms)
169
+ min mean[+/-sd] median max
170
+ Connect: 0 0 0.0 0 0
171
+ Processing: 1004 1091 39.8 1089 1163
172
+ Waiting: 1004 1091 39.8 1089 1163
173
+ Total: 1004 1091 39.8 1089 1163
174
+
175
+ Percentage of the requests served within a certain time (ms)
176
+ 50% 1089
177
+ 66% 1113
178
+ 75% 1120
179
+ 80% 1139
180
+ 90% 1149
181
+ 95% 1156
182
+ 98% 1163
183
+ 99% 1163
184
+ 100% 1163 (longest request)
185
+ ```
186
+
187
+ ### Using graphicsmagick
188
+
189
+ ```
190
+ Document Path: /images/big/full/!500,500/0/default.jpg
191
+ Document Length: 82841 bytes
192
+
193
+ Concurrency Level: 1
194
+ Time taken for tests: 39.825 seconds
195
+ Complete requests: 50
196
+ Failed requests: 0
197
+ Total transferred: 4186581 bytes
198
+ HTML transferred: 4142050 bytes
199
+ Requests per second: 1.26 [#/sec] (mean)
200
+ Time per request: 796.495 [ms] (mean)
201
+ Time per request: 796.495 [ms] (mean, across all concurrent requests)
202
+ Transfer rate: 102.66 [Kbytes/sec] received
203
+
204
+ Connection Times (ms)
205
+ min mean[+/-sd] median max
206
+ Connect: 0 0 0.0 0 0
207
+ Processing: 642 796 66.2 800 924
208
+ Waiting: 642 796 66.2 800 924
209
+ Total: 642 796 66.2 800 924
210
+
211
+ Percentage of the requests served within a certain time (ms)
212
+ 50% 800
213
+ 66% 832
214
+ 75% 845
215
+ 80% 859
216
+ 90% 880
217
+ 95% 899
218
+ 98% 924
219
+ 99% 924
220
+ 100% 924 (longest request)
221
+ ```
222
+
223
+ ### Using libvips
224
+
225
+ ```
226
+ Document Path: /images/big/full/!500,500/0/default.jpg
227
+ Document Length: 81878 bytes
228
+
229
+ Concurrency Level: 1
230
+ Time taken for tests: 6.661 seconds
231
+ Complete requests: 50
232
+ Failed requests: 0
233
+ Total transferred: 4138502 bytes
234
+ HTML transferred: 4093900 bytes
235
+ Requests per second: 7.51 [#/sec] (mean)
236
+ Time per request: 133.222 [ms] (mean)
237
+ Time per request: 133.222 [ms] (mean, across all concurrent requests)
238
+ Transfer rate: 606.73 [Kbytes/sec] received
239
+
240
+ Connection Times (ms)
241
+ min mean[+/-sd] median max
242
+ Connect: 0 0 0.0 0 0
243
+ Processing: 115 133 12.0 130 160
244
+ Waiting: 115 133 12.0 130 160
245
+ Total: 115 133 12.0 130 160
246
+
247
+ Percentage of the requests served within a certain time (ms)
248
+ 50% 130
249
+ 66% 141
250
+ 75% 143
251
+ 80% 146
252
+ 90% 150
253
+ 95% 154
254
+ 98% 160
255
+ 99% 160
256
+ 100% 160 (longest request)
257
+ ```
data/lib/riiif/engine.rb CHANGED
@@ -10,5 +10,19 @@ module Riiif
10
10
 
11
11
  # Set to true to use kdu for jp2000 source images
12
12
  config.kakadu_enabled = false
13
+
14
+ # Set to true to use libvips to transform images
15
+ # https://www.libvips.org/
16
+ config.use_vips = false
17
+
18
+ config.before_configuration do
19
+ # see https://github.com/fxn/zeitwerk#for_gem
20
+ # We put a generator into LOCAL APP lib/generators, so tell
21
+ # zeitwerk to ignore the whole directory? If we're using a recent
22
+ # enough version of Rails to have zeitwerk config
23
+ #
24
+ # See: https://github.com/cbeer/engine_cart/issues/117
25
+ Rails.autoloaders.main.ignore(Rails.root.join('lib', 'generators')) if Rails.try(:autoloaders).try(:main).respond_to?(:ignore)
26
+ end
13
27
  end
14
28
  end
data/lib/riiif/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Riiif
2
- VERSION = '2.1.0'.freeze
2
+ VERSION = '2.8.1'.freeze
3
3
  end
data/lib/riiif.rb CHANGED
@@ -12,10 +12,24 @@ module Riiif
12
12
  # This error is raised when Riiif can't convert an image
13
13
  class ConversionError < Error; end
14
14
 
15
+ def self.deprecation
16
+ @deprecation ||= ActiveSupport::Deprecation.new('3.0', 'Riiif')
17
+ end
18
+
19
+ HTTPFileResolver = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
20
+ 'Riiif::HTTPFileResolver',
21
+ 'Riiif::HttpFileResolver',
22
+ Riiif.deprecation
23
+ )
24
+
15
25
  mattr_accessor :not_found_image # the image to use when a lookup fails
16
26
  mattr_accessor :unauthorized_image # the image to use when a user doesn't have access
17
27
 
18
28
  def self.kakadu_enabled?
19
29
  Engine.config.kakadu_enabled
20
30
  end
31
+
32
+ def self.use_vips?
33
+ Engine.config.use_vips
34
+ end
21
35
  end
data/riiif.gemspec CHANGED
@@ -7,10 +7,10 @@ Gem::Specification.new do |spec|
7
7
  spec.name = 'riiif'
8
8
  spec.version = Riiif::VERSION
9
9
  spec.authors = ['Justin Coyne']
10
- spec.email = ['administrator@curationexperts.com']
10
+ spec.email = ['jcoyne85@stanford.edu']
11
11
  spec.description = 'A IIIF image server'
12
- spec.summary = 'A rails engine that support IIIF requests'
13
- spec.homepage = 'https://github.com/curationexperts/riiif'
12
+ spec.summary = 'A Rails engine that support IIIF requests'
13
+ spec.homepage = 'https://github.com/sul-dlss/riiif'
14
14
  spec.license = 'APACHE2'
15
15
 
16
16
  spec.files = `git ls-files|grep -v spec/samples`.split($INPUT_RECORD_SEPARATOR)
@@ -18,14 +18,16 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency 'railties', '>= 4.2', '<6'
21
+ spec.add_dependency 'railties', '>= 4.2', '< 9'
22
22
  spec.add_dependency 'deprecation', '>= 1.0.0'
23
- spec.add_dependency 'iiif-image-api', '~> 0.1.0'
23
+ spec.add_dependency 'iiif-image-api', '>= 0.1.0'
24
+ spec.add_dependency 'ruby-vips'
25
+
24
26
  spec.add_development_dependency 'bundler'
25
27
  spec.add_development_dependency 'rake'
26
28
  spec.add_development_dependency 'engine_cart', '~> 2.0'
27
29
  spec.add_development_dependency 'rspec-rails'
28
30
  spec.add_development_dependency 'sqlite3'
29
- spec.add_development_dependency 'bixby', '~> 1.0.0'
30
- spec.add_development_dependency 'coveralls'
31
+ spec.add_development_dependency 'bixby', '~> 5.0'
32
+ spec.add_development_dependency 'simplecov'
31
33
  end
@@ -24,9 +24,9 @@ RSpec.describe Riiif::ImagesController do
24
24
  it 'sends images to the service' do
25
25
  image = double
26
26
  expect(Riiif::Image).to receive(:new).with('abcd1234').and_return(image)
27
- expect(image).to receive(:render).with('region' => 'full', 'size' => 'full',
27
+ expect(image).to receive(:render).with({ 'region' => 'full', 'size' => 'full',
28
28
  'rotation' => '0', 'quality' => 'default',
29
- 'format' => 'jpg').and_return('IMAGEDATA')
29
+ 'format' => 'jpg' }).and_return('IMAGEDATA')
30
30
  get :show, params: { id: 'abcd1234', action: 'show', region: 'full', size: 'full',
31
31
  rotation: '0', quality: 'default', format: 'jpg' }
32
32
  expect(response).to be_successful
@@ -102,9 +102,9 @@ RSpec.describe Riiif::ImagesController do
102
102
  raise Riiif::ImageNotFoundError unless file.present?
103
103
  not_found_image
104
104
  end.twice
105
- expect(not_found_image).to receive(:render).with('region' => 'full', 'size' => 'full',
105
+ expect(not_found_image).to receive(:render).with({ 'region' => 'full', 'size' => 'full',
106
106
  'rotation' => '0', 'quality' => 'default',
107
- 'format' => 'jpg').and_return('default-image-data')
107
+ 'format' => 'jpg' }).and_return('default-image-data')
108
108
 
109
109
  get :show, params: { id: 'bad_id', action: 'show', region: 'full', size: 'full',
110
110
  rotation: '0', quality: 'default', format: 'jpg' }
@@ -118,9 +118,9 @@ RSpec.describe Riiif::ImagesController do
118
118
  raise Riiif::ImageNotFoundError unless file.present?
119
119
  not_found_image
120
120
  end.twice
121
- expect(not_found_image).to receive(:render).with('region' => 'full', 'size' => 'full',
121
+ expect(not_found_image).to receive(:render).with({ 'region' => 'full', 'size' => 'full',
122
122
  'rotation' => '0', 'quality' => 'default',
123
- 'format' => 'jpg').and_return('default-image-data')
123
+ 'format' => 'jpg' }).and_return('default-image-data')
124
124
 
125
125
  get :show, params: { id: 'bad_id', action: 'show', region: 'full', size: 'full',
126
126
  rotation: '0', quality: 'default', format: 'jpg' }
@@ -155,12 +155,12 @@ RSpec.describe Riiif::ImagesController do
155
155
  get :info, params: { id: 'abcd1234', format: 'json' }
156
156
  expect(response).to be_successful
157
157
  expect(json).to eq '@context' => 'http://iiif.io/api/image/2/context.json',
158
- '@id' => 'http://test.host/abcd1234',
158
+ '@id' => Rails.version > '6.1' ? 'http://test.host/images/abcd1234' : 'http://test.host/abcd1234',
159
159
  'width' => 6000,
160
160
  'height' => 4000,
161
161
  'format' => 'JPEG',
162
162
  'channels' => 'rgb',
163
- 'profile' => ['http://iiif.io/api/image/2/level1.json', 'formats' => %w(jpg png)],
163
+ 'profile' => ['http://iiif.io/api/image/2/level1.json', 'formats' => %w(webp jpg png)],
164
164
  'protocol' => 'http://iiif.io/api/image'
165
165
  expect(response.headers['Link']).to eq '<http://iiif.io/api/image/2/level1.json>;rel="profile"'
166
166
  expect(response.headers['Content-Type']).to eq 'application/ld+json; charset=utf-8'
@@ -0,0 +1,31 @@
1
+ RSpec.describe Riiif::ImageMagickInfoExtractor do
2
+ it 'uses identify as its external command' do
3
+ expect(described_class.external_command).to eq "identify"
4
+ end
5
+
6
+ context 'with a jpg' do
7
+ let(:image) { Rails.root.join("spec", "fixtures", "test.jpg") }
8
+
9
+ it 'returns the extracted attributes' do
10
+ expect(described_class.new(image).extract).to eq({
11
+ height: 397,
12
+ width: 300,
13
+ format: "JPEG",
14
+ channels: "srgb"
15
+ })
16
+ end
17
+ end
18
+
19
+ context 'with a png' do
20
+ let(:image) { Rails.root.join("spec", "fixtures", "test.png") }
21
+
22
+ it 'returns the extracted attributes' do
23
+ expect(described_class.new(image).extract).to eq({
24
+ height: 50,
25
+ width: 50,
26
+ format: "PNG",
27
+ channels: "srgba"
28
+ })
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Riiif::VipsInfoExtractor do
4
+ before do
5
+ allow(Riiif::CommandRunner).to receive(:execute).and_return(fake_info)
6
+ allow(Vips::Image).to receive(:new_from_file).and_return(image)
7
+ end
8
+
9
+ let(:image) { double(has_alpha?: false) }
10
+
11
+ let(:fake_info) do
12
+ "width: 500
13
+ height: 376
14
+ interpretation: srgb
15
+ filename: spec/fixtures/test.tif
16
+ vips-loader: tiffload"
17
+ end
18
+
19
+ it 'uses vipsheader as its external command' do
20
+ expect(described_class.external_command).to eq "vipsheader"
21
+ end
22
+
23
+ context 'on a file without transparency' do
24
+ it 'returns the extracted attributes' do
25
+ expect(described_class.new("path/to/image.jpg").extract).to eq({
26
+ height: 376,
27
+ width: 500,
28
+ format: "JPEG",
29
+ channels: "srgb"
30
+ })
31
+ end
32
+ end
33
+
34
+ context 'on a file with transparency' do
35
+ let(:image) { double(has_alpha?: true) }
36
+
37
+ let(:fake_info) do
38
+ "width: 50
39
+ height: 50
40
+ interpretation: srgb
41
+ filename: spec/fixtures/test.tif
42
+ vips-loader: pngload"
43
+ end
44
+
45
+ it 'returns the extracted attributes' do
46
+ expect(described_class.new(image).extract).to eq({
47
+ height: 50,
48
+ width: 50,
49
+ format: "PNG",
50
+ channels: "srgba"
51
+ })
52
+ end
53
+ end
54
+
55
+ context 'on a JPEG file with extra EXIF metadata' do
56
+ let(:image) { double(has_alpha?: false) }
57
+
58
+ let(:fake_info) do
59
+ "width: 150
60
+ height: 150
61
+ interpretation: srgb
62
+ filename: spec/fixtures/test.jpeg
63
+ vips-loader: jpegload
64
+ exif-ifd0-Artist: University Library (Digital Object: Digital Media Group)"
65
+ end
66
+
67
+ it 'returns the extracted attributes' do
68
+ expect(described_class.new(image).extract).to eq({
69
+ height: 150,
70
+ width: 150,
71
+ format: "JPEG",
72
+ channels: "srgb"
73
+ })
74
+ end
75
+ end
76
+ end
Binary file
Binary file
Binary file