riiif 2.7.0 → 2.8.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/.rubocop_todo.yml +7 -1
- data/README.md +26 -4
- data/app/extractors/riiif/abstract_info_extractor.rb +12 -0
- data/app/{services → extractors}/riiif/image_magick_info_extractor.rb +1 -6
- data/app/extractors/riiif/vips_info_extractor.rb +22 -0
- data/app/models/riiif/file.rb +11 -2
- data/app/services/riiif/crop.rb +40 -0
- data/app/services/riiif/vips_resize.rb +47 -0
- data/app/transformers/riiif/vips_transformer.rb +103 -0
- data/docs/vips_comparison.md +257 -0
- data/lib/riiif/engine.rb +4 -0
- data/lib/riiif/version.rb +1 -1
- data/lib/riiif.rb +4 -0
- data/riiif.gemspec +1 -0
- data/spec/extractors/riiif/image_magick_info_extractor_spec.rb +31 -0
- data/spec/extractors/riiif/vips_info_extractor_spec.rb +54 -0
- data/spec/fixtures/test.jpg +0 -0
- data/spec/fixtures/test.png +0 -0
- data/spec/fixtures/test.tif +0 -0
- data/spec/models/riiif/file_spec.rb +35 -0
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +6 -1
- data/spec/transformers/riiif/vips_transformer_spec.rb +271 -0
- metadata +37 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5ac6417bc393f31ac0789724dda5bfbacb41223dc22dc9b1f0979d18b66ec9fe
|
|
4
|
+
data.tar.gz: c33036fd69be61f884277e37a907e51817c35a1a7a213fd9808ad1207f67d6a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: af382985fd204ea8ed65a499f2c70f1e47e861bff2dfeb60ba292ccd34544e44ce7e1d222edd2d40cc1fbf53e9cea7dc3fccd11350b54d3746627ca68179f876
|
|
7
|
+
data.tar.gz: 80e1d79683e44cd0fd177ea8a444981643f0caae86dc41df24430fc04d0a8b82146f97cbfd0e5456084b73a55099e2d80040d96288aecffb2d3dbcd9a5f356bd
|
data/.rubocop_todo.yml
CHANGED
|
@@ -69,7 +69,13 @@ Lint/SuppressedException:
|
|
|
69
69
|
Metrics/BlockLength:
|
|
70
70
|
Max: 237
|
|
71
71
|
|
|
72
|
-
# Offense count:
|
|
72
|
+
# Offense count: 2
|
|
73
|
+
Metrics/ClassLength:
|
|
74
|
+
Exclude:
|
|
75
|
+
- 'app/services/riiif/crop.rb'
|
|
76
|
+
- 'app/services/riiif/resize.rb'
|
|
77
|
+
|
|
78
|
+
# Offense count: 1
|
|
73
79
|
# Configuration parameters: IgnoredMethods.
|
|
74
80
|
Metrics/CyclomaticComplexity:
|
|
75
81
|
Max: 7
|
data/README.md
CHANGED
|
@@ -5,7 +5,9 @@ A Ruby IIIF image server as a rails engine.
|
|
|
5
5
|
|
|
6
6
|
## Installation
|
|
7
7
|
|
|
8
|
-
RIIIF
|
|
8
|
+
To use RIIIF, you need to install at least one of Imagemagick, Graphicsmagick, or Vips (libvips) (see [comparison](docs/vips_comparison.md)). By default, RIIIF will use Imagemagick.
|
|
9
|
+
|
|
10
|
+
To install Imagemagick on a Mac using Homebrew you can follow these instructions:
|
|
9
11
|
|
|
10
12
|
ImageMagick (7.0.4) may be installed with a few options:
|
|
11
13
|
* `--with-ghostscript` Compile with Ghostscript for Postscript/PDF support
|
|
@@ -31,6 +33,16 @@ Or install it yourself as:
|
|
|
31
33
|
|
|
32
34
|
## Configure
|
|
33
35
|
|
|
36
|
+
Any of the following code examples should be included in a `config/initializers/riiif.rb` file in your application (or somewhere else loaded in your app environment) like so:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
Rails.application.config.to_prepare do
|
|
40
|
+
# code goes here
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For test applications generated by engine cart (see [Running the tests](#running-the-tests)), this would be `<cloned repo folder>/.internal_test_app/config/initializers/riiif.rb`.
|
|
45
|
+
|
|
34
46
|
### Images on the servers file system.
|
|
35
47
|
|
|
36
48
|
By default Riiif is set to load images from the filesystem using the Riiif::FileSystemFileResolver.
|
|
@@ -75,15 +87,25 @@ See [benchmark](docs/benchmark.md) for details
|
|
|
75
87
|
|
|
76
88
|
To use [GraphicsMagick](http://www.graphicsmagick.org/) instead of ImageMagick
|
|
77
89
|
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
```ruby
|
|
91
|
+
Riiif::ImagemagickCommandFactory.external_command = "gm convert"
|
|
92
|
+
Riiif::ImageMagickInfoExtractor.external_command = "gm identify"
|
|
93
|
+
```
|
|
80
94
|
|
|
81
95
|
You will of course need to install GraphicsMagick on your system.
|
|
82
96
|
|
|
97
|
+
### Libvips (aka Vips)
|
|
98
|
+
|
|
99
|
+
To use [libvips](https://www.libvips.org/) instead of ImageMagick
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
Riiif::Engine.config.use_vips = true
|
|
103
|
+
```
|
|
104
|
+
|
|
83
105
|
## Usage
|
|
84
106
|
|
|
85
107
|
Add the routes to your application by inserting the following line into `config/routes.rb`
|
|
86
|
-
```
|
|
108
|
+
```ruby
|
|
87
109
|
mount Riiif::Engine => '/image-service', as: 'riiif'
|
|
88
110
|
```
|
|
89
111
|
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
module Riiif
|
|
2
2
|
# Get information using imagemagick to interrogate the file
|
|
3
|
-
class ImageMagickInfoExtractor
|
|
3
|
+
class ImageMagickInfoExtractor < AbstractInfoExtractor
|
|
4
4
|
# perhaps you want to use GraphicsMagick instead, set to "gm identify"
|
|
5
|
-
class_attribute :external_command
|
|
6
5
|
self.external_command = 'identify'
|
|
7
6
|
|
|
8
|
-
def initialize(path)
|
|
9
|
-
@path = path
|
|
10
|
-
end
|
|
11
|
-
|
|
12
7
|
def extract
|
|
13
8
|
height, width, format, channels = Riiif::CommandRunner.execute(
|
|
14
9
|
"#{external_command} -format '%h %w %m %[channels]' '#{@path}[0]'"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'ruby-vips' if Riiif::Engine.config.use_vips
|
|
2
|
+
|
|
3
|
+
module Riiif
|
|
4
|
+
# Get information using (lib)vips to interrogate the file
|
|
5
|
+
class VipsInfoExtractor < AbstractInfoExtractor
|
|
6
|
+
self.external_command = 'vipsheader'
|
|
7
|
+
|
|
8
|
+
def extract
|
|
9
|
+
attributes = Riiif::CommandRunner.execute("#{external_command} '#{@path}' -a")
|
|
10
|
+
.split(/\n/)
|
|
11
|
+
.map { |str| str.strip.split(': ') }.to_h
|
|
12
|
+
width, height = attributes.values_at("width", "height")
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
height: Integer(height),
|
|
16
|
+
width: Integer(width),
|
|
17
|
+
format: attributes["vips-loader"].match?("pngload") ? "PNG" : "JPEG",
|
|
18
|
+
channels: ::Vips::Image.new_from_file(@path.to_s).has_alpha? ? "srgba" : "srgb"
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/app/models/riiif/file.rb
CHANGED
|
@@ -4,7 +4,14 @@ module Riiif
|
|
|
4
4
|
|
|
5
5
|
class_attribute :info_extractor_class
|
|
6
6
|
# TODO: add alternative that uses kdu_jp2info
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
def self.info_extractor_class
|
|
9
|
+
if Riiif.use_vips?
|
|
10
|
+
VipsInfoExtractor
|
|
11
|
+
else
|
|
12
|
+
ImageMagickInfoExtractor
|
|
13
|
+
end
|
|
14
|
+
end
|
|
8
15
|
|
|
9
16
|
# @param input_path [String] The location of an image file
|
|
10
17
|
def initialize(input_path, tempfile = nil)
|
|
@@ -20,7 +27,9 @@ module Riiif
|
|
|
20
27
|
end
|
|
21
28
|
|
|
22
29
|
def transformer
|
|
23
|
-
if Riiif.
|
|
30
|
+
if Riiif.use_vips?
|
|
31
|
+
VipsTransformer
|
|
32
|
+
elsif Riiif.kakadu_enabled? && path.ends_with?('.jp2')
|
|
24
33
|
KakaduTransformer
|
|
25
34
|
else
|
|
26
35
|
ImagemagickTransformer
|
data/app/services/riiif/crop.rb
CHANGED
|
@@ -45,8 +45,35 @@ module Riiif
|
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
def to_vips
|
|
49
|
+
case region
|
|
50
|
+
when IIIF::Image::Region::Full
|
|
51
|
+
nil
|
|
52
|
+
when IIIF::Image::Region::Absolute
|
|
53
|
+
[region.offset_x, region.offset_y, region.width, region.height]
|
|
54
|
+
when IIIF::Image::Region::Square
|
|
55
|
+
vips_square
|
|
56
|
+
when IIIF::Image::Region::Percent
|
|
57
|
+
vips_percent
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
48
61
|
private
|
|
49
62
|
|
|
63
|
+
def vips_percent
|
|
64
|
+
# Calculate x values
|
|
65
|
+
offset_x, width = [region.x_pct, region.width_pct].map do |percent|
|
|
66
|
+
(image_info.width * percentage_to_fraction(percent)).round
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Calculate y values
|
|
70
|
+
offset_y, height = [region.y_pct, region.height_pct].map do |percent|
|
|
71
|
+
(image_info.height * percentage_to_fraction(percent)).round
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
[offset_x, offset_y, width, height]
|
|
75
|
+
end
|
|
76
|
+
|
|
50
77
|
def imagemagick_percent
|
|
51
78
|
offset_x = (image_info.width * percentage_to_fraction(region.x_pct)).round
|
|
52
79
|
offset_y = (image_info.height * percentage_to_fraction(region.y_pct)).round
|
|
@@ -60,6 +87,19 @@ module Riiif
|
|
|
60
87
|
"\{#{percentage_to_fraction(region.height_pct)},#{percentage_to_fraction(region.width_pct)}\}"
|
|
61
88
|
end
|
|
62
89
|
|
|
90
|
+
def vips_square
|
|
91
|
+
min, max = [image_info.width, image_info.height].minmax
|
|
92
|
+
offset = (max - min) / 2
|
|
93
|
+
|
|
94
|
+
if image_info.height >= image_info.width
|
|
95
|
+
# Portrait: left, offset, width, height
|
|
96
|
+
[0, offset, min, min]
|
|
97
|
+
else
|
|
98
|
+
# Landscape: left, offset, width, height
|
|
99
|
+
[offset, 0, min, min]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
63
103
|
def kakadu_square
|
|
64
104
|
min, max = [image_info.width, image_info.height].minmax
|
|
65
105
|
offset = (max - min) / 2
|
|
@@ -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
|
@@ -11,6 +11,10 @@ module Riiif
|
|
|
11
11
|
# Set to true to use kdu for jp2000 source images
|
|
12
12
|
config.kakadu_enabled = false
|
|
13
13
|
|
|
14
|
+
# Set to true to use libvips to transform images
|
|
15
|
+
# https://www.libvips.org/
|
|
16
|
+
config.use_vips = false
|
|
17
|
+
|
|
14
18
|
config.before_configuration do
|
|
15
19
|
# see https://github.com/fxn/zeitwerk#for_gem
|
|
16
20
|
# We put a generator into LOCAL APP lib/generators, so tell
|
data/lib/riiif/version.rb
CHANGED
data/lib/riiif.rb
CHANGED
data/riiif.gemspec
CHANGED
|
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
spec.add_dependency 'railties', '>= 4.2', '< 9'
|
|
22
22
|
spec.add_dependency 'deprecation', '>= 1.0.0'
|
|
23
23
|
spec.add_dependency 'iiif-image-api', '>= 0.1.0'
|
|
24
|
+
spec.add_dependency 'ruby-vips'
|
|
24
25
|
|
|
25
26
|
spec.add_development_dependency 'bundler'
|
|
26
27
|
spec.add_development_dependency 'rake'
|
|
@@ -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,54 @@
|
|
|
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
|
+
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
RSpec.describe Riiif::File do
|
|
2
|
+
describe '#info_extractor_class' do
|
|
3
|
+
subject { described_class.info_extractor_class }
|
|
4
|
+
|
|
5
|
+
context 'when not using vips' do
|
|
6
|
+
it { is_expected.to eq Riiif::ImageMagickInfoExtractor }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
context 'when vips is configured' do
|
|
10
|
+
before { allow(Riiif).to receive(:use_vips?).and_return true }
|
|
11
|
+
|
|
12
|
+
it { is_expected.to eq Riiif::VipsInfoExtractor }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe '#transformer' do
|
|
17
|
+
subject { described_class.new('path.jp2', double).transformer }
|
|
18
|
+
|
|
19
|
+
context 'when vips is configured' do
|
|
20
|
+
before { allow(Riiif).to receive(:use_vips?).and_return true }
|
|
21
|
+
|
|
22
|
+
it { is_expected.to eq Riiif::VipsTransformer }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context 'when Kakadu is enabled' do
|
|
26
|
+
before { allow(Riiif).to receive(:kakadu_enabled?).and_return true }
|
|
27
|
+
|
|
28
|
+
it { is_expected.to eq Riiif::KakaduTransformer }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context 'when using image/graphicsmagick without Kakadu' do
|
|
32
|
+
it { is_expected.to eq Riiif::ImagemagickTransformer }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
require 'rails/generators'
|
|
2
2
|
|
|
3
3
|
class TestAppGenerator < Rails::Generators::Base
|
|
4
|
-
source_root 'spec/test_app_templates'
|
|
4
|
+
# source_root 'spec/test_app_templates'
|
|
5
|
+
source_root File.expand_path("../../../spec", __dir__)
|
|
5
6
|
|
|
6
7
|
def add_routes
|
|
7
8
|
route "mount Riiif::Engine => '/images', as: 'riiif'"
|
|
8
9
|
end
|
|
10
|
+
|
|
11
|
+
def copy_fixtures
|
|
12
|
+
directory 'fixtures', 'spec/fixtures'
|
|
13
|
+
end
|
|
9
14
|
end
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'ruby-vips'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
module Vips
|
|
7
|
+
class Image
|
|
8
|
+
# Intentionally blank.
|
|
9
|
+
#
|
|
10
|
+
# This prevents uninitialized constant errors if vips
|
|
11
|
+
# is not installed.
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
RSpec.describe Riiif::VipsTransformer do
|
|
17
|
+
let(:channels) { 'rgb' }
|
|
18
|
+
|
|
19
|
+
let(:path) { "path/to/image.tif" }
|
|
20
|
+
|
|
21
|
+
let(:image) { double('Vips Image', has_alpha?: false) }
|
|
22
|
+
|
|
23
|
+
let(:image_info) do
|
|
24
|
+
double({ height: 376,
|
|
25
|
+
width: 500,
|
|
26
|
+
format: 'jpg',
|
|
27
|
+
channels: channels })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
let(:target) { 'jpg' }
|
|
31
|
+
|
|
32
|
+
let(:transformation) do
|
|
33
|
+
IIIF::Image::Transformation.new(region: region,
|
|
34
|
+
size: size,
|
|
35
|
+
rotation: rotation,
|
|
36
|
+
format: target)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Default/Placeholder values that should be modified in tests
|
|
40
|
+
let(:size) { IIIF::Image::Size::Full.new }
|
|
41
|
+
let(:region) { IIIF::Image::Region::Full.new }
|
|
42
|
+
let(:rotation) { 0 }
|
|
43
|
+
|
|
44
|
+
before do
|
|
45
|
+
allow(Vips::Image).to receive(:new_from_file).and_return(image)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#initialize' do
|
|
49
|
+
let(:path) { Pathname.new("path/to/image.tif") }
|
|
50
|
+
|
|
51
|
+
it 'normalizes pathnames to strings' do
|
|
52
|
+
expect(Vips::Image).to receive(:new_from_file).with("path/to/image.tif")
|
|
53
|
+
described_class.new(path, image_info, transformation)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe '#transform' do
|
|
58
|
+
subject { described_class.new(path, image_info, transformation).transform }
|
|
59
|
+
before { allow(image).to receive(:write_to_buffer) }
|
|
60
|
+
after { subject }
|
|
61
|
+
|
|
62
|
+
context 'when requesting jpg format with default options' do
|
|
63
|
+
it 'writes to jpg format' do
|
|
64
|
+
expect(image).to receive(:write_to_buffer).with(".jpg[Q=85,optimize-coding,strip]")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context 'when requesting png format with default options' do
|
|
69
|
+
let(:target) { 'png' }
|
|
70
|
+
|
|
71
|
+
it 'writes to png format' do
|
|
72
|
+
expect(image).to receive(:write_to_buffer).with(".png[Q=85,strip]")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context 'when requesting jpeg format for a png' do
|
|
77
|
+
let(:image) { double('Vips Image', has_alpha?: true) }
|
|
78
|
+
|
|
79
|
+
it 'writes to png anyway to preserve transparency' do
|
|
80
|
+
expect(image).to receive(:write_to_buffer).with(".png[Q=85,strip]")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context 'with subsampling turned off' do
|
|
85
|
+
subject { described_class.new(path, image_info, transformation, subsample: false).transform }
|
|
86
|
+
|
|
87
|
+
it 'does not subsample' do
|
|
88
|
+
expect(image).to receive(:write_to_buffer).with(".jpg[Q=85,optimize-coding,strip,no-subsample]")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
context 'when specifying compression factor' do
|
|
93
|
+
subject { described_class.new(path, image_info, transformation, compression: 90).transform }
|
|
94
|
+
|
|
95
|
+
it 'compresses to the correct quality' do
|
|
96
|
+
expect(image).to receive(:write_to_buffer).with(".jpg[Q=90,optimize-coding,strip]")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context 'when strip_metadata is false' do
|
|
101
|
+
subject { described_class.new(path, image_info, transformation, strip_metadata: false).transform }
|
|
102
|
+
|
|
103
|
+
it 'does not strip metadata' do
|
|
104
|
+
expect(image).to receive(:write_to_buffer).with(".jpg[Q=85,optimize-coding]")
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe '#transform_image' do
|
|
110
|
+
subject { described_class.new(path, image_info, transformation).send(:transform_image) }
|
|
111
|
+
|
|
112
|
+
before do
|
|
113
|
+
allow(image).to receive_messages(crop: image, resize: image, rotate: image, thumbnail_image: image, colourspace: image)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe 'resize' do
|
|
117
|
+
context 'when specifing full size' do
|
|
118
|
+
it 'does not resize' do
|
|
119
|
+
expect(image).not_to receive(:resize)
|
|
120
|
+
expect(image).not_to receive(:thumbnail_image)
|
|
121
|
+
subject
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
context 'when specifing percent size' do
|
|
126
|
+
let(:size) { IIIF::Image::Size::Percent.new(50) }
|
|
127
|
+
|
|
128
|
+
it 'resizes the image' do
|
|
129
|
+
expect(image).to receive(:resize).with(50.0)
|
|
130
|
+
expect(image).not_to receive(:thumbnail_image)
|
|
131
|
+
subject
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
context 'when specifing float percent size' do
|
|
136
|
+
let(:size) { IIIF::Image::Size::Percent.new(12.5) }
|
|
137
|
+
|
|
138
|
+
it 'resizes the image' do
|
|
139
|
+
expect(image).to receive(:resize).with(12.5)
|
|
140
|
+
expect(image).not_to receive(:thumbnail_image)
|
|
141
|
+
subject
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context 'when specifying width and/or height' do
|
|
146
|
+
context 'when specifing w, size' do
|
|
147
|
+
let(:size) { IIIF::Image::Size::Width.new(300) }
|
|
148
|
+
|
|
149
|
+
before { allow(image).to receive(:width).and_return(600) }
|
|
150
|
+
|
|
151
|
+
it 'resizes the image to 300px wide, maintaining aspect ratio' do
|
|
152
|
+
expect(image).to receive(:resize).with(0.5)
|
|
153
|
+
subject
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
context 'when specifing ,h size' do
|
|
158
|
+
let(:size) { IIIF::Image::Size::Height.new(200) }
|
|
159
|
+
|
|
160
|
+
before { allow(image).to receive(:height).and_return(500) }
|
|
161
|
+
|
|
162
|
+
it 'resizes the image to 300px high, maintaining aspect ratio' do
|
|
163
|
+
expect(image).to receive(:resize).with(0.4)
|
|
164
|
+
subject
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
context 'when specifing absolute w,h size' do
|
|
169
|
+
let(:size) { IIIF::Image::Size::Absolute.new(200, 300) }
|
|
170
|
+
|
|
171
|
+
it 'resizes the image, ignoring aspect ratio' do
|
|
172
|
+
expect(image).to receive(:thumbnail_image).with(200, height: 300, size: :force)
|
|
173
|
+
subject
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
context 'when specifing bestfit (!w,h) size' do
|
|
178
|
+
let(:size) { IIIF::Image::Size::BestFit.new(200, 300) }
|
|
179
|
+
|
|
180
|
+
it 'resizes the image so that the width and height are equal or less than the requested value' do
|
|
181
|
+
expect(image).to receive(:thumbnail_image).with(200, height: 300)
|
|
182
|
+
subject
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
describe 'crop' do
|
|
189
|
+
after { subject }
|
|
190
|
+
|
|
191
|
+
context 'when specifing full size' do
|
|
192
|
+
let(:region) { IIIF::Image::Region::Full.new }
|
|
193
|
+
|
|
194
|
+
it 'does not crop' do
|
|
195
|
+
expect(image).not_to receive(:crop)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
context 'when specifing absolute geometry' do
|
|
200
|
+
let(:region) { IIIF::Image::Region::Absolute.new(80, 15, 60, 75) }
|
|
201
|
+
|
|
202
|
+
it 'crops to that region' do
|
|
203
|
+
expect(image).to receive(:crop).with(80, 15, 60, 75)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
context 'when specifing percent geometry' do
|
|
208
|
+
let(:region) { IIIF::Image::Region::Percent.new(10, 10, 80, 70) }
|
|
209
|
+
before { allow(image_info).to receive_messages(width: 100, height: 100, format: 'jpeg', channels: channels) }
|
|
210
|
+
|
|
211
|
+
it 'crops to that region' do
|
|
212
|
+
expect(image).to receive(:crop).with(10, 10, 80, 70)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
context 'when specifing square geometry' do
|
|
217
|
+
let(:region) { IIIF::Image::Region::Square.new }
|
|
218
|
+
|
|
219
|
+
it 'crops a square the size of the shortest edge' do
|
|
220
|
+
expect(image).to receive(:crop).with(62, 0, 376, 376)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
describe 'rotate' do
|
|
226
|
+
after { subject }
|
|
227
|
+
|
|
228
|
+
context 'when no rotation (0) is specified' do
|
|
229
|
+
it 'does not rotate' do
|
|
230
|
+
expect(image).not_to receive(:rotate)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
context 'when rotation is specified' do
|
|
235
|
+
let(:rotation) { 45 }
|
|
236
|
+
|
|
237
|
+
it 'rotates the image' do
|
|
238
|
+
expect(image).to receive(:rotate).with(45)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
describe 'colourspace' do
|
|
244
|
+
after { subject }
|
|
245
|
+
|
|
246
|
+
context 'when quality is default or color' do
|
|
247
|
+
it 'leaves the image in color' do
|
|
248
|
+
expect(image).not_to receive(:colourspace).with(:b_w)
|
|
249
|
+
expect(image).not_to receive(:>)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
context 'when quality is gray' do
|
|
254
|
+
let(:transformation) { IIIF::Image::Transformation.new(region: region, size: size, rotation: rotation, quality: 'gray') }
|
|
255
|
+
|
|
256
|
+
it 'makes the image grayscale' do
|
|
257
|
+
expect(image).to receive(:colourspace).with(:b_w)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
context 'when quality is bitonal' do
|
|
262
|
+
let(:transformation) { IIIF::Image::Transformation.new(region: region, size: size, rotation: rotation, quality: 'bitonal') }
|
|
263
|
+
|
|
264
|
+
it 'makes the image bitonal' do
|
|
265
|
+
expect(image).to receive(:colourspace).with(:b_w)
|
|
266
|
+
expect(image).to receive(:>).with(200)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: riiif
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Coyne
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: railties
|
|
@@ -58,6 +57,20 @@ dependencies:
|
|
|
58
57
|
- - ">="
|
|
59
58
|
- !ruby/object:Gem::Version
|
|
60
59
|
version: 0.1.0
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: ruby-vips
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
type: :runtime
|
|
68
|
+
prerelease: false
|
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '0'
|
|
61
74
|
- !ruby/object:Gem::Dependency
|
|
62
75
|
name: bundler
|
|
63
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -174,6 +187,9 @@ files:
|
|
|
174
187
|
- README.md
|
|
175
188
|
- Rakefile
|
|
176
189
|
- app/controllers/riiif/images_controller.rb
|
|
190
|
+
- app/extractors/riiif/abstract_info_extractor.rb
|
|
191
|
+
- app/extractors/riiif/image_magick_info_extractor.rb
|
|
192
|
+
- app/extractors/riiif/vips_info_extractor.rb
|
|
177
193
|
- app/models/riiif/file.rb
|
|
178
194
|
- app/models/riiif/image.rb
|
|
179
195
|
- app/models/riiif/image_information.rb
|
|
@@ -183,17 +199,19 @@ files:
|
|
|
183
199
|
- app/resolvers/riiif/http_file_resolver.rb
|
|
184
200
|
- app/services/riiif/command_runner.rb
|
|
185
201
|
- app/services/riiif/crop.rb
|
|
186
|
-
- app/services/riiif/image_magick_info_extractor.rb
|
|
187
202
|
- app/services/riiif/imagemagick_command_factory.rb
|
|
188
203
|
- app/services/riiif/kakadu_command_factory.rb
|
|
189
204
|
- app/services/riiif/link_name_service.rb
|
|
190
205
|
- app/services/riiif/nil_authorization_service.rb
|
|
191
206
|
- app/services/riiif/resize.rb
|
|
207
|
+
- app/services/riiif/vips_resize.rb
|
|
192
208
|
- app/transformers/riiif/abstract_transformer.rb
|
|
193
209
|
- app/transformers/riiif/imagemagick_transformer.rb
|
|
194
210
|
- app/transformers/riiif/kakadu_transformer.rb
|
|
211
|
+
- app/transformers/riiif/vips_transformer.rb
|
|
195
212
|
- config/routes.rb
|
|
196
213
|
- docs/benchmark.md
|
|
214
|
+
- docs/vips_comparison.md
|
|
197
215
|
- lib/riiif.rb
|
|
198
216
|
- lib/riiif/engine.rb
|
|
199
217
|
- lib/riiif/rails/routes.rb
|
|
@@ -201,7 +219,13 @@ files:
|
|
|
201
219
|
- lib/riiif/version.rb
|
|
202
220
|
- riiif.gemspec
|
|
203
221
|
- spec/controllers/riiif/images_controller_spec.rb
|
|
222
|
+
- spec/extractors/riiif/image_magick_info_extractor_spec.rb
|
|
223
|
+
- spec/extractors/riiif/vips_info_extractor_spec.rb
|
|
224
|
+
- spec/fixtures/test.jpg
|
|
225
|
+
- spec/fixtures/test.png
|
|
226
|
+
- spec/fixtures/test.tif
|
|
204
227
|
- spec/models/riiif/akubra_system_file_resolver_spec.rb
|
|
228
|
+
- spec/models/riiif/file_spec.rb
|
|
205
229
|
- spec/models/riiif/file_system_file_resolver_spec.rb
|
|
206
230
|
- spec/models/riiif/http_file_resolver_spec.rb
|
|
207
231
|
- spec/models/riiif/image_information_spec.rb
|
|
@@ -214,11 +238,11 @@ files:
|
|
|
214
238
|
- spec/test_app_templates/Gemfile.extra
|
|
215
239
|
- spec/test_app_templates/lib/generators/test_app_generator.rb
|
|
216
240
|
- spec/transformers/riiif/kakadu_transformer_spec.rb
|
|
241
|
+
- spec/transformers/riiif/vips_transformer_spec.rb
|
|
217
242
|
homepage: https://github.com/sul-dlss/riiif
|
|
218
243
|
licenses:
|
|
219
244
|
- APACHE2
|
|
220
245
|
metadata: {}
|
|
221
|
-
post_install_message:
|
|
222
246
|
rdoc_options: []
|
|
223
247
|
require_paths:
|
|
224
248
|
- lib
|
|
@@ -233,13 +257,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
233
257
|
- !ruby/object:Gem::Version
|
|
234
258
|
version: '0'
|
|
235
259
|
requirements: []
|
|
236
|
-
rubygems_version: 3.
|
|
237
|
-
signing_key:
|
|
260
|
+
rubygems_version: 3.6.9
|
|
238
261
|
specification_version: 4
|
|
239
262
|
summary: A Rails engine that support IIIF requests
|
|
240
263
|
test_files:
|
|
241
264
|
- spec/controllers/riiif/images_controller_spec.rb
|
|
265
|
+
- spec/extractors/riiif/image_magick_info_extractor_spec.rb
|
|
266
|
+
- spec/extractors/riiif/vips_info_extractor_spec.rb
|
|
267
|
+
- spec/fixtures/test.jpg
|
|
268
|
+
- spec/fixtures/test.png
|
|
269
|
+
- spec/fixtures/test.tif
|
|
242
270
|
- spec/models/riiif/akubra_system_file_resolver_spec.rb
|
|
271
|
+
- spec/models/riiif/file_spec.rb
|
|
243
272
|
- spec/models/riiif/file_system_file_resolver_spec.rb
|
|
244
273
|
- spec/models/riiif/http_file_resolver_spec.rb
|
|
245
274
|
- spec/models/riiif/image_information_spec.rb
|
|
@@ -252,3 +281,4 @@ test_files:
|
|
|
252
281
|
- spec/test_app_templates/Gemfile.extra
|
|
253
282
|
- spec/test_app_templates/lib/generators/test_app_generator.rb
|
|
254
283
|
- spec/transformers/riiif/kakadu_transformer_spec.rb
|
|
284
|
+
- spec/transformers/riiif/vips_transformer_spec.rb
|