riiif 1.1.1 → 1.1.2
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.yml +8 -0
- data/.rubocop_todo.yml +1 -10
- data/app/controllers/riiif/images_controller.rb +3 -1
- data/app/models/riiif/file.rb +13 -33
- data/app/models/riiif/image.rb +24 -30
- data/app/services/riiif/command_runner.rb +28 -0
- data/app/services/riiif/image_magick_info_extractor.rb +13 -0
- data/app/services/riiif/region/imagemagick/absolute_decoder.rb +21 -0
- data/app/services/riiif/region/imagemagick/full_decoder.rb +14 -0
- data/app/services/riiif/region/imagemagick/percentage_decoder.rb +33 -0
- data/app/services/riiif/region/imagemagick/square_decoder.rb +25 -0
- data/app/services/riiif/size/imagemagick/absolute_decoder.rb +20 -0
- data/app/services/riiif/size/imagemagick/best_fit_decoder.rb +19 -0
- data/app/services/riiif/size/imagemagick/full_decoder.rb +14 -0
- data/app/services/riiif/size/imagemagick/height_decoder.rb +19 -0
- data/app/services/riiif/size/imagemagick/percent_decoder.rb +19 -0
- data/app/services/riiif/size/imagemagick/width_decoder.rb +19 -0
- data/lib/riiif/routes.rb +3 -1
- data/lib/riiif/version.rb +1 -1
- data/lib/riiif.rb +2 -0
- data/riiif.gemspec +2 -2
- data/spec/models/riiif/akubra_system_file_resolver_spec.rb +2 -1
- data/spec/models/riiif/image_spec.rb +29 -23
- metadata +19 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ad33e2e0d258079efa25eef4e206dd4099996dd
|
|
4
|
+
data.tar.gz: 960a01a9a73dcf961ce4c945f6a2fcd64adca6ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d6ead011f1e16fd7559fd67df911a1e964a4a8f17c73a549e4f9473b511021b675bbbe6b731cb86528a899138445a27427aab42e1a264ffb8e5b7d1869ba10d
|
|
7
|
+
data.tar.gz: 58eea4d035b7ac49adb4fa8fc91537e76114ee76d025fde5538502e0bfb9c5e672cae5b109a9a63ad0ddbd853652820b9612b7ac2297c7055eb40ac29865cd47
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
|
@@ -28,11 +28,6 @@ Lint/UselessAssignment:
|
|
|
28
28
|
Metrics/AbcSize:
|
|
29
29
|
Max: 38
|
|
30
30
|
|
|
31
|
-
# Offense count: 12
|
|
32
|
-
# Configuration parameters: CountComments, ExcludedMethods.
|
|
33
|
-
Metrics/BlockLength:
|
|
34
|
-
Max: 143
|
|
35
|
-
|
|
36
31
|
# Offense count: 1
|
|
37
32
|
# Configuration parameters: CountComments.
|
|
38
33
|
Metrics/ClassLength:
|
|
@@ -46,17 +41,13 @@ Metrics/CyclomaticComplexity:
|
|
|
46
41
|
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
|
47
42
|
# URISchemes: http, https
|
|
48
43
|
Metrics/LineLength:
|
|
49
|
-
Max:
|
|
44
|
+
Max: 120
|
|
50
45
|
|
|
51
46
|
# Offense count: 7
|
|
52
47
|
# Configuration parameters: CountComments.
|
|
53
48
|
Metrics/MethodLength:
|
|
54
49
|
Max: 21
|
|
55
50
|
|
|
56
|
-
# Offense count: 2
|
|
57
|
-
Metrics/PerceivedComplexity:
|
|
58
|
-
Max: 9
|
|
59
|
-
|
|
60
51
|
# Offense count: 1
|
|
61
52
|
# Cop supports --auto-correct.
|
|
62
53
|
Performance/RedundantMatch:
|
|
@@ -59,7 +59,9 @@ module Riiif
|
|
|
59
59
|
##
|
|
60
60
|
# @return [ActiveSupport::HashWithIndifferentAccess]
|
|
61
61
|
def image_request_params
|
|
62
|
-
params.permit(:region, :size, :rotation, :quality, :format).to_h
|
|
62
|
+
result = params.permit(:region, :size, :rotation, :quality, :format).to_h
|
|
63
|
+
return result.with_indifferent_access if Rails.version < '5'
|
|
64
|
+
result
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
def authorization_service
|
data/app/models/riiif/file.rb
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
require 'open3'
|
|
2
1
|
module Riiif
|
|
3
2
|
class File
|
|
4
|
-
include Open3
|
|
5
|
-
include ActiveSupport::Benchmarkable
|
|
6
|
-
|
|
7
3
|
attr_reader :path
|
|
8
4
|
|
|
9
|
-
|
|
5
|
+
class_attribute :info_extractor_class
|
|
6
|
+
self.info_extractor_class = ImageMagickInfoExtractor
|
|
10
7
|
|
|
11
8
|
# @param input_path [String] The location of an image file
|
|
12
9
|
def initialize(input_path, tempfile = nil)
|
|
@@ -34,48 +31,31 @@ module Riiif
|
|
|
34
31
|
|
|
35
32
|
end
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
# @param [Transformation] transformation
|
|
35
|
+
def extract(transformation)
|
|
39
36
|
command = 'convert'
|
|
40
|
-
command << " -crop #{
|
|
41
|
-
command << " -resize #{
|
|
42
|
-
if
|
|
43
|
-
command << " -virtual-pixel white +distort srt #{
|
|
37
|
+
command << " -crop #{transformation.crop}" if transformation.crop
|
|
38
|
+
command << " -resize #{transformation.size}" if transformation.size
|
|
39
|
+
if transformation.rotation
|
|
40
|
+
command << " -virtual-pixel white +distort srt #{transformation.rotation}"
|
|
44
41
|
end
|
|
45
42
|
|
|
46
|
-
case
|
|
43
|
+
case transformation.quality
|
|
47
44
|
when 'grey'
|
|
48
45
|
command << ' -colorspace Gray'
|
|
49
46
|
when 'bitonal'
|
|
50
47
|
command << ' -colorspace Gray'
|
|
51
48
|
command << ' -type Bilevel'
|
|
52
49
|
end
|
|
53
|
-
command << " #{path} #{
|
|
50
|
+
command << " #{path} #{transformation.format}:-"
|
|
54
51
|
execute(command)
|
|
55
52
|
end
|
|
56
53
|
|
|
57
54
|
def info
|
|
58
|
-
|
|
59
|
-
height, width = execute("identify -format %hx%w #{path}").split('x')
|
|
60
|
-
@info = { height: Integer(height), width: Integer(width) }
|
|
55
|
+
@info ||= info_extractor_class.new(path).extract
|
|
61
56
|
end
|
|
62
57
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def execute(command)
|
|
66
|
-
out = nil
|
|
67
|
-
benchmark("Riiif executed #{command}") do
|
|
68
|
-
stdin, stdout, stderr, wait_thr = popen3(command)
|
|
69
|
-
stdin.close
|
|
70
|
-
stdout.binmode
|
|
71
|
-
out = stdout.read
|
|
72
|
-
stdout.close
|
|
73
|
-
err = stderr.read
|
|
74
|
-
stderr.close
|
|
75
|
-
raise "Unable to execute command \"#{command}\"\n#{err}" unless wait_thr.value.success?
|
|
76
|
-
end
|
|
77
|
-
out
|
|
78
|
-
end
|
|
79
|
-
|
|
58
|
+
delegate :execute, to: Riiif::CommandRunner
|
|
59
|
+
private :execute
|
|
80
60
|
end
|
|
81
61
|
end
|
data/app/models/riiif/image.rb
CHANGED
|
@@ -42,7 +42,10 @@ module Riiif
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def info
|
|
45
|
-
|
|
45
|
+
@info ||= begin
|
|
46
|
+
result = info_service.call(id, image)
|
|
47
|
+
ImageInformation.new(result[:width], result[:height])
|
|
48
|
+
end
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
class << self
|
|
@@ -52,7 +55,7 @@ module Riiif
|
|
|
52
55
|
|
|
53
56
|
|
|
54
57
|
def cache_key(id, options)
|
|
55
|
-
str = options.merge(id: id).delete_if { |_, v| v.nil? }.to_s
|
|
58
|
+
str = options.to_h.merge(id: id).delete_if { |_, v| v.nil? }.to_s
|
|
56
59
|
# Use a MD5 digest to ensure the keys aren't too long.
|
|
57
60
|
Digest::MD5.hexdigest(str)
|
|
58
61
|
end
|
|
@@ -62,14 +65,15 @@ module Riiif
|
|
|
62
65
|
|
|
63
66
|
##
|
|
64
67
|
# @param [ActiveSupport::HashWithIndifferentAccess] options
|
|
68
|
+
# @return [Transformation]
|
|
65
69
|
def decode_options!(options)
|
|
66
70
|
raise ArgumentError, "You must provide a format. You provided #{options}" unless options[:format]
|
|
67
|
-
options[:crop] = decode_region(options.delete(:region))
|
|
68
|
-
options[:size] = decode_size(options.delete(:size))
|
|
69
|
-
options[:quality] = decode_quality(options[:quality])
|
|
70
|
-
options[:rotation] = decode_rotation(options[:rotation])
|
|
71
71
|
validate_format!(options[:format])
|
|
72
|
-
options
|
|
72
|
+
Riiif::Transformation.new(decode_region(options.delete(:region)),
|
|
73
|
+
decode_size(options.delete(:size)),
|
|
74
|
+
decode_quality(options[:quality]),
|
|
75
|
+
decode_rotation(options[:rotation]),
|
|
76
|
+
options[:format])
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
def decode_quality(quality)
|
|
@@ -94,47 +98,37 @@ module Riiif
|
|
|
94
98
|
|
|
95
99
|
def decode_region(region)
|
|
96
100
|
if region.nil? || region == 'full'
|
|
97
|
-
|
|
101
|
+
Riiif::Region::Imagemagick::FullDecoder.new.decode
|
|
98
102
|
elsif md = /^pct:(\d+),(\d+),(\d+),(\d+)$/.match(region)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
offset_y = (info[:height] * Integer(md[2]).to_f / 100).round
|
|
102
|
-
"#{md[3]}%x#{md[4]}+#{offset_x}+#{offset_y}"
|
|
103
|
+
Riiif::Region::Imagemagick::PercentageDecoder
|
|
104
|
+
.new(info, md[1], md[2], md[3], md[4]).decode
|
|
103
105
|
elsif md = /^(\d+),(\d+),(\d+),(\d+)$/.match(region)
|
|
104
|
-
|
|
106
|
+
Riiif::Region::Imagemagick::AbsoluteDecoder.new(md[1], md[2], md[3], md[4]).decode
|
|
105
107
|
elsif region == 'square'
|
|
106
|
-
|
|
107
|
-
h = info[:height]
|
|
108
|
-
min, max = [w, h].minmax
|
|
109
|
-
|
|
110
|
-
offset = (max - min) / 2
|
|
111
|
-
if h >= w
|
|
112
|
-
"#{min}x#{min}+0+#{offset}"
|
|
113
|
-
else
|
|
114
|
-
"#{min}x#{min}+#{offset}+0"
|
|
115
|
-
end
|
|
108
|
+
Riiif::Region::Imagemagick::SquareDecoder.new(info).decode
|
|
116
109
|
else
|
|
117
110
|
raise InvalidAttributeError, "Invalid region: #{region}"
|
|
118
111
|
end
|
|
119
112
|
end
|
|
120
113
|
|
|
114
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
121
115
|
def decode_size(size)
|
|
122
116
|
if size.nil? || size == 'full'
|
|
123
|
-
|
|
117
|
+
Riiif::Size::Imagemagick::FullDecoder.new.decode
|
|
124
118
|
elsif md = /^,(\d+)$/.match(size)
|
|
125
|
-
|
|
119
|
+
Riiif::Size::Imagemagick::HeightDecoder.new(md[1]).decode
|
|
126
120
|
elsif md = /^(\d+),$/.match(size)
|
|
127
|
-
(md[1]).
|
|
121
|
+
Riiif::Size::Imagemagick::WidthDecoder.new(md[1]).decode
|
|
128
122
|
elsif md = /^pct:(\d+(.\d+)?)$/.match(size)
|
|
129
|
-
|
|
123
|
+
Riiif::Size::Imagemagick::PercentDecoder.new(md[1]).decode
|
|
130
124
|
elsif md = /^(\d+),(\d+)$/.match(size)
|
|
131
|
-
|
|
125
|
+
Riiif::Size::Imagemagick::AbsoluteDecoder.new(md[1], md[2]).decode
|
|
132
126
|
elsif md = /^!(\d+),(\d+)$/.match(size)
|
|
133
|
-
|
|
127
|
+
Riiif::Size::Imagemagick::BestFitDecoder.new(md[1], md[2]).decode
|
|
134
128
|
else
|
|
135
129
|
raise InvalidAttributeError, "Invalid size: #{size}"
|
|
136
130
|
end
|
|
137
131
|
end
|
|
138
|
-
|
|
132
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
139
133
|
end
|
|
140
134
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
module Riiif
|
|
3
|
+
# Runs shell commands under benchmark and saves the output
|
|
4
|
+
class CommandRunner
|
|
5
|
+
include Open3
|
|
6
|
+
include ActiveSupport::Benchmarkable
|
|
7
|
+
delegate :logger, to: :Rails
|
|
8
|
+
|
|
9
|
+
def execute(command)
|
|
10
|
+
out = nil
|
|
11
|
+
benchmark("Riiif executed #{command}") do
|
|
12
|
+
stdin, stdout, stderr, wait_thr = popen3(command)
|
|
13
|
+
stdin.close
|
|
14
|
+
stdout.binmode
|
|
15
|
+
out = stdout.read
|
|
16
|
+
stdout.close
|
|
17
|
+
err = stderr.read
|
|
18
|
+
stderr.close
|
|
19
|
+
raise "Unable to execute command \"#{command}\"\n#{err}" unless wait_thr.value.success?
|
|
20
|
+
end
|
|
21
|
+
out
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.execute(command)
|
|
25
|
+
new.execute(command)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
# Get height and width information using imagemagick to interrogate the file
|
|
3
|
+
class ImageMagickInfoExtractor
|
|
4
|
+
def initialize(path)
|
|
5
|
+
@path = path
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def extract
|
|
9
|
+
height, width = Riiif::CommandRunner.execute("identify -format %hx%w #{@path}").split('x')
|
|
10
|
+
{ height: Integer(height), width: Integer(width) }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
module Region
|
|
3
|
+
module Imagemagick
|
|
4
|
+
# decodes requested cooridnates into an imagemagick crop directive
|
|
5
|
+
class AbsoluteDecoder
|
|
6
|
+
def initialize(x, y, width, height)
|
|
7
|
+
@x = x
|
|
8
|
+
@y = y
|
|
9
|
+
@width = width
|
|
10
|
+
@height = height
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [String] a region for imagemagick to decode
|
|
14
|
+
# (appropriate for passing to the -crop parameter)
|
|
15
|
+
def decode
|
|
16
|
+
"#{@width}x#{@height}+#{@x}+#{@y}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
module Region
|
|
3
|
+
module Imagemagick
|
|
4
|
+
# The image or region is not scaled, and is returned at its full size.
|
|
5
|
+
class FullDecoder
|
|
6
|
+
# @return [NilClass] a region for imagemagick to decode
|
|
7
|
+
# the nil implies no cropping needed
|
|
8
|
+
def decode
|
|
9
|
+
nil
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
module Region
|
|
3
|
+
module Imagemagick
|
|
4
|
+
# decodes requested cooridnates into an imagemagick crop directive
|
|
5
|
+
class PercentageDecoder
|
|
6
|
+
def initialize(image_info, x, y, width, height)
|
|
7
|
+
@image_info = image_info
|
|
8
|
+
@x = x
|
|
9
|
+
@y = y
|
|
10
|
+
@width = width
|
|
11
|
+
@height = height
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Imagemagick can't do percentage offsets, so we have to calculate it
|
|
15
|
+
# @return [String] a region for imagemagick to decode
|
|
16
|
+
# (appropriate for passing to the -crop parameter)
|
|
17
|
+
def decode
|
|
18
|
+
"#{@width}%x#{@height}+#{offset_x}+#{offset_y}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def offset_x
|
|
24
|
+
(@image_info.width * Integer(@x).to_f / 100).round
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def offset_y
|
|
28
|
+
(@image_info.height * Integer(@y).to_f / 100).round
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
module Region
|
|
3
|
+
module Imagemagick
|
|
4
|
+
# decodes requested cooridnates into an imagemagick crop directive
|
|
5
|
+
class SquareDecoder
|
|
6
|
+
def initialize(image_info)
|
|
7
|
+
@image_info = image_info
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# @return [String] a square region for imagemagick to decode
|
|
11
|
+
# (appropriate for passing to the -crop parameter)
|
|
12
|
+
def decode
|
|
13
|
+
min, max = [@image_info.width, @image_info.height].minmax
|
|
14
|
+
|
|
15
|
+
offset = (max - min) / 2
|
|
16
|
+
if @image_info.height >= @image_info.width
|
|
17
|
+
"#{min}x#{min}+0+#{offset}"
|
|
18
|
+
else
|
|
19
|
+
"#{min}x#{min}+#{offset}+0"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
module Size
|
|
3
|
+
module Imagemagick
|
|
4
|
+
# The width and height of the returned image are exactly w and h.
|
|
5
|
+
# The aspect ratio of the returned image may be different than the extracted
|
|
6
|
+
# region, resulting in a distorted image.
|
|
7
|
+
class AbsoluteDecoder
|
|
8
|
+
def initialize(width, height)
|
|
9
|
+
@width = width
|
|
10
|
+
@height = height
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [String] a resize directive for imagemagick to use
|
|
14
|
+
def decode
|
|
15
|
+
"#{@width}x#{@height}!"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
module Size
|
|
3
|
+
module Imagemagick
|
|
4
|
+
# The image content is scaled for the best fit such that the resulting width and
|
|
5
|
+
# height are less than or equal to the requested width and height.
|
|
6
|
+
class BestFitDecoder
|
|
7
|
+
def initialize(width, height)
|
|
8
|
+
@width = width
|
|
9
|
+
@height = height
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @return [String] a resize directive for imagemagick to use
|
|
13
|
+
def decode
|
|
14
|
+
"#{@width}x#{@height}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
module Size
|
|
3
|
+
module Imagemagick
|
|
4
|
+
# decodes requested size into an imagemagick resize directive
|
|
5
|
+
class FullDecoder
|
|
6
|
+
# @return [NilClass] a size for imagemagick to decode
|
|
7
|
+
# the nil implies no resizing needed
|
|
8
|
+
def decode
|
|
9
|
+
nil
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
module Size
|
|
3
|
+
module Imagemagick
|
|
4
|
+
# The image or region should be scaled so that its height is exactly equal
|
|
5
|
+
# to the provided parameter, and the width will be a calculated value that
|
|
6
|
+
# maintains the aspect ratio of the extracted region
|
|
7
|
+
class HeightDecoder
|
|
8
|
+
def initialize(height)
|
|
9
|
+
@height = height
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @return [String] a resize directive for imagemagick to use
|
|
13
|
+
def decode
|
|
14
|
+
"x#{@height}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
module Size
|
|
3
|
+
module Imagemagick
|
|
4
|
+
# The width and height of the returned image is scaled to n% of the width and height
|
|
5
|
+
# of the extracted region. The aspect ratio of the returned image is the same as that
|
|
6
|
+
# of the extracted region.
|
|
7
|
+
class PercentDecoder
|
|
8
|
+
def initialize(n)
|
|
9
|
+
@n = n
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @return [String] a resize directive for imagemagick to use
|
|
13
|
+
def decode
|
|
14
|
+
"#{@n}%"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Riiif
|
|
2
|
+
module Size
|
|
3
|
+
module Imagemagick
|
|
4
|
+
# The image or region should be scaled so that its width is exactly equal
|
|
5
|
+
# to the provided parameter, and the height will be a calculated value that
|
|
6
|
+
# maintains the aspect ratio of the extracted region
|
|
7
|
+
class WidthDecoder
|
|
8
|
+
def initialize(width)
|
|
9
|
+
@width = width
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @return [String] a resize directive for imagemagick to use
|
|
13
|
+
def decode
|
|
14
|
+
@width.to_s
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/riiif/routes.rb
CHANGED
|
@@ -22,7 +22,9 @@ module Riiif
|
|
|
22
22
|
defaults: { format: 'jpg', rotation: '0', region: 'full', quality: 'default', model: resource },
|
|
23
23
|
as: options[:as] || 'image'
|
|
24
24
|
|
|
25
|
-
get "#{route_prefix}/:id/info.json" => 'riiif/images#info',
|
|
25
|
+
get "#{route_prefix}/:id/info.json" => 'riiif/images#info',
|
|
26
|
+
defaults: { format: 'json', model: resource },
|
|
27
|
+
as: [options[:as], 'info'].compact.join('_')
|
|
26
28
|
|
|
27
29
|
# This doesn't work presently
|
|
28
30
|
# get "#{route_prefix}/:id", to: redirect("#{route_prefix}/%{id}/info.json")
|
data/lib/riiif/version.rb
CHANGED
data/lib/riiif.rb
CHANGED
data/riiif.gemspec
CHANGED
|
@@ -24,6 +24,6 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.add_development_dependency 'engine_cart', '~> 0.8'
|
|
25
25
|
spec.add_development_dependency 'rspec-rails'
|
|
26
26
|
spec.add_development_dependency 'sqlite3'
|
|
27
|
-
spec.add_development_dependency 'rubocop', '~> 0.
|
|
28
|
-
spec.add_development_dependency 'rubocop-rspec', '~> 1.
|
|
27
|
+
spec.add_development_dependency 'rubocop', '~> 0.47.1'
|
|
28
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.13'
|
|
29
29
|
end
|
|
@@ -7,6 +7,7 @@ describe Riiif::AkubraSystemFileResolver do
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
it 'gets the jpeg2000 file' do
|
|
10
|
-
|
|
10
|
+
file = Dir.glob(subject.pathroot + '22/7e/9/info%3Afedora%2Fdemo%3A1%2Fjp2%2Fjp2.0').first
|
|
11
|
+
expect(subject.find('demo:1').path).to eq Riiif::File.new(file).path
|
|
11
12
|
end
|
|
12
13
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
describe Riiif::Image do
|
|
3
|
+
RSpec.describe Riiif::Image do
|
|
4
4
|
before { Rails.cache.clear }
|
|
5
5
|
let(:filename) { File.expand_path('spec/samples/world.jp2') }
|
|
6
6
|
subject { described_class.new('world') }
|
|
@@ -16,7 +16,7 @@ describe Riiif::Image do
|
|
|
16
16
|
it 'is able to override the file used for the Image' do
|
|
17
17
|
img = described_class.new('some_id', Riiif::File.new(filename))
|
|
18
18
|
expect(img.id).to eq 'some_id'
|
|
19
|
-
expect(img.info).to eq
|
|
19
|
+
expect(img.info).to eq Riiif::ImageInformation.new(800, 400)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
describe 'without a format' do
|
|
@@ -27,7 +27,7 @@ describe Riiif::Image do
|
|
|
27
27
|
|
|
28
28
|
describe 'info' do
|
|
29
29
|
it 'returns the data' do
|
|
30
|
-
expect(subject.info).to eq
|
|
30
|
+
expect(subject.info).to eq Riiif::ImageInformation.new(800, 400)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -45,7 +45,7 @@ describe Riiif::Image do
|
|
|
45
45
|
describe 'get info' do
|
|
46
46
|
subject { described_class.new('Cave_26,_Ajanta') }
|
|
47
47
|
it 'is easy' do
|
|
48
|
-
expect(subject.info).to eq
|
|
48
|
+
expect(subject.info).to eq Riiif::ImageInformation.new(600, 390)
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
@@ -63,21 +63,25 @@ describe Riiif::Image do
|
|
|
63
63
|
describe 'mogrify' do
|
|
64
64
|
describe 'region' do
|
|
65
65
|
it 'returns the original when specifing full size' do
|
|
66
|
-
expect(
|
|
66
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert #{filename} png:-")
|
|
67
67
|
subject.render(region: 'full', format: 'png')
|
|
68
68
|
end
|
|
69
69
|
it 'handles absolute geometry' do
|
|
70
|
-
expect(
|
|
70
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert -crop 60x75+80+15 #{filename} png:-")
|
|
71
71
|
subject.render(region: '80,15,60,75', format: 'png')
|
|
72
72
|
end
|
|
73
|
+
|
|
73
74
|
it 'handles percent geometry' do
|
|
74
|
-
expect(
|
|
75
|
-
|
|
75
|
+
expect(Riiif::CommandRunner).to receive(:execute)
|
|
76
|
+
.with("identify -format %hx%w #{filename}").and_return('131x175')
|
|
77
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert -crop 80%x70+18+13 #{filename} png:-")
|
|
76
78
|
subject.render(region: 'pct:10,10,80,70', format: 'png')
|
|
77
79
|
end
|
|
80
|
+
|
|
78
81
|
it 'handles square geometry' do
|
|
79
|
-
expect(
|
|
80
|
-
|
|
82
|
+
expect(Riiif::CommandRunner).to receive(:execute)
|
|
83
|
+
.with("identify -format %hx%w #{filename}").and_return('131x175')
|
|
84
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert -crop 131x131+22+0 #{filename} png:-")
|
|
81
85
|
subject.render(region: 'square', format: 'png')
|
|
82
86
|
end
|
|
83
87
|
it 'raises an error for invalid geometry' do
|
|
@@ -87,31 +91,31 @@ describe Riiif::Image do
|
|
|
87
91
|
|
|
88
92
|
describe 'resize' do
|
|
89
93
|
it 'returns the original when specifing full size' do
|
|
90
|
-
expect(
|
|
94
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert #{filename} png:-")
|
|
91
95
|
subject.render(size: 'full', format: 'png')
|
|
92
96
|
end
|
|
93
97
|
it 'handles integer percent sizes' do
|
|
94
|
-
expect(
|
|
98
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert -resize 50% #{filename} png:-")
|
|
95
99
|
subject.render(size: 'pct:50', format: 'png')
|
|
96
100
|
end
|
|
97
101
|
it 'handles float percent sizes' do
|
|
98
|
-
expect(
|
|
102
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert -resize 12.5% #{filename} png:-")
|
|
99
103
|
subject.render(size: 'pct:12.5', format: 'png')
|
|
100
104
|
end
|
|
101
105
|
it 'handles w,' do
|
|
102
|
-
expect(
|
|
106
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert -resize 50 #{filename} png:-")
|
|
103
107
|
subject.render(size: '50,', format: 'png')
|
|
104
108
|
end
|
|
105
109
|
it 'handles ,h' do
|
|
106
|
-
expect(
|
|
110
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert -resize x50 #{filename} png:-")
|
|
107
111
|
subject.render(size: ',50', format: 'png')
|
|
108
112
|
end
|
|
109
113
|
it 'handles w,h' do
|
|
110
|
-
expect(
|
|
114
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert -resize 150x75! #{filename} png:-")
|
|
111
115
|
subject.render(size: '150,75', format: 'png')
|
|
112
116
|
end
|
|
113
117
|
it 'handles bestfit (!w,h)' do
|
|
114
|
-
expect(
|
|
118
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert -resize 150x75 #{filename} png:-")
|
|
115
119
|
subject.render(size: '!150,75', format: 'png')
|
|
116
120
|
end
|
|
117
121
|
it 'raises an error for invalid size' do
|
|
@@ -121,11 +125,12 @@ describe Riiif::Image do
|
|
|
121
125
|
|
|
122
126
|
describe 'rotate' do
|
|
123
127
|
it 'returns the original when specifing full size' do
|
|
124
|
-
expect(
|
|
128
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert #{filename} png:-")
|
|
125
129
|
subject.render(rotation: '0', format: 'png')
|
|
126
130
|
end
|
|
127
131
|
it 'handles floats' do
|
|
128
|
-
expect(
|
|
132
|
+
expect(Riiif::CommandRunner).to receive(:execute)
|
|
133
|
+
.with("convert -virtual-pixel white +distort srt 22.5 #{filename} png:-")
|
|
129
134
|
subject.render(rotation: '22.5', format: 'png')
|
|
130
135
|
end
|
|
131
136
|
it 'raises an error for invalid angle' do
|
|
@@ -135,19 +140,20 @@ describe Riiif::Image do
|
|
|
135
140
|
|
|
136
141
|
describe 'quality' do
|
|
137
142
|
it 'returns the original when specifing default' do
|
|
138
|
-
expect(
|
|
143
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert #{filename} png:-")
|
|
139
144
|
subject.render(quality: 'default', format: 'png')
|
|
140
145
|
end
|
|
141
146
|
it 'returns the original when specifing color' do
|
|
142
|
-
expect(
|
|
147
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert #{filename} png:-")
|
|
143
148
|
subject.render(quality: 'color', format: 'png')
|
|
144
149
|
end
|
|
145
150
|
it 'converts to grayscale' do
|
|
146
|
-
expect(
|
|
151
|
+
expect(Riiif::CommandRunner).to receive(:execute).with("convert -colorspace Gray #{filename} png:-")
|
|
147
152
|
subject.render(quality: 'grey', format: 'png')
|
|
148
153
|
end
|
|
149
154
|
it 'converts to bitonal' do
|
|
150
|
-
expect(
|
|
155
|
+
expect(Riiif::CommandRunner).to receive(:execute)
|
|
156
|
+
.with("convert -colorspace Gray -type Bilevel #{filename} png:-")
|
|
151
157
|
subject.render(quality: 'bitonal', format: 'png')
|
|
152
158
|
end
|
|
153
159
|
it 'raises an error for invalid angle' do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: riiif
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Coyne
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-03-
|
|
11
|
+
date: 2017-03-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: railties
|
|
@@ -106,28 +106,28 @@ dependencies:
|
|
|
106
106
|
requirements:
|
|
107
107
|
- - "~>"
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: 0.
|
|
109
|
+
version: 0.47.1
|
|
110
110
|
type: :development
|
|
111
111
|
prerelease: false
|
|
112
112
|
version_requirements: !ruby/object:Gem::Requirement
|
|
113
113
|
requirements:
|
|
114
114
|
- - "~>"
|
|
115
115
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: 0.
|
|
116
|
+
version: 0.47.1
|
|
117
117
|
- !ruby/object:Gem::Dependency
|
|
118
118
|
name: rubocop-rspec
|
|
119
119
|
requirement: !ruby/object:Gem::Requirement
|
|
120
120
|
requirements:
|
|
121
121
|
- - "~>"
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: '1.
|
|
123
|
+
version: '1.13'
|
|
124
124
|
type: :development
|
|
125
125
|
prerelease: false
|
|
126
126
|
version_requirements: !ruby/object:Gem::Requirement
|
|
127
127
|
requirements:
|
|
128
128
|
- - "~>"
|
|
129
129
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: '1.
|
|
130
|
+
version: '1.13'
|
|
131
131
|
description: A IIIF image server
|
|
132
132
|
email:
|
|
133
133
|
- justin@curationexperts.com
|
|
@@ -148,6 +148,18 @@ files:
|
|
|
148
148
|
- app/controllers/riiif/images_controller.rb
|
|
149
149
|
- app/models/riiif/file.rb
|
|
150
150
|
- app/models/riiif/image.rb
|
|
151
|
+
- app/services/riiif/command_runner.rb
|
|
152
|
+
- app/services/riiif/image_magick_info_extractor.rb
|
|
153
|
+
- app/services/riiif/region/imagemagick/absolute_decoder.rb
|
|
154
|
+
- app/services/riiif/region/imagemagick/full_decoder.rb
|
|
155
|
+
- app/services/riiif/region/imagemagick/percentage_decoder.rb
|
|
156
|
+
- app/services/riiif/region/imagemagick/square_decoder.rb
|
|
157
|
+
- app/services/riiif/size/imagemagick/absolute_decoder.rb
|
|
158
|
+
- app/services/riiif/size/imagemagick/best_fit_decoder.rb
|
|
159
|
+
- app/services/riiif/size/imagemagick/full_decoder.rb
|
|
160
|
+
- app/services/riiif/size/imagemagick/height_decoder.rb
|
|
161
|
+
- app/services/riiif/size/imagemagick/percent_decoder.rb
|
|
162
|
+
- app/services/riiif/size/imagemagick/width_decoder.rb
|
|
151
163
|
- config/routes.rb
|
|
152
164
|
- lib/riiif.rb
|
|
153
165
|
- lib/riiif/abstract_file_system_resolver.rb
|
|
@@ -190,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
190
202
|
version: '0'
|
|
191
203
|
requirements: []
|
|
192
204
|
rubyforge_project:
|
|
193
|
-
rubygems_version: 2.6.
|
|
205
|
+
rubygems_version: 2.6.8
|
|
194
206
|
signing_key:
|
|
195
207
|
specification_version: 4
|
|
196
208
|
summary: A rails engine that support IIIF requests
|