riiif 1.7.1 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +15 -41
- data/Gemfile +1 -37
- data/app/models/riiif/file.rb +19 -12
- data/app/models/riiif/image.rb +13 -12
- data/app/models/riiif/image_information.rb +4 -0
- data/app/models/riiif/transformation.rb +35 -0
- data/{lib → app/resolvers}/riiif/abstract_file_system_resolver.rb +0 -0
- data/{lib → app/resolvers}/riiif/akubra_system_file_resolver.rb +0 -0
- data/{lib → app/resolvers}/riiif/file_system_file_resolver.rb +0 -0
- data/{lib → app/resolvers}/riiif/http_file_resolver.rb +0 -0
- data/app/services/riiif/command_runner.rb +2 -0
- data/app/services/riiif/crop.rb +54 -0
- data/app/services/riiif/imagemagick_command_factory.rb +20 -19
- data/app/services/riiif/imagemagick_transformer.rb +8 -0
- data/app/services/riiif/kakadu_command_factory.rb +63 -0
- data/app/services/riiif/link_name_service.rb +8 -0
- data/{lib → app/services}/riiif/nil_authorization_service.rb +0 -0
- data/app/services/riiif/option_decoder.rb +11 -11
- data/app/services/riiif/region/absolute.rb +23 -0
- data/app/services/riiif/region/full.rb +23 -0
- data/app/services/riiif/region/percentage.rb +68 -0
- data/app/services/riiif/region/square.rb +45 -0
- data/app/services/riiif/resize.rb +45 -0
- data/app/services/riiif/size/absolute.rb +39 -0
- data/app/services/riiif/size/best_fit.rb +18 -0
- data/app/services/riiif/size/full.rb +17 -0
- data/app/services/riiif/size/height.rb +24 -0
- data/app/services/riiif/size/percent.rb +44 -0
- data/app/services/riiif/size/width.rb +24 -0
- data/app/transformers/riiif/abstract_transformer.rb +30 -0
- data/app/transformers/riiif/imagemagick_transformer.rb +8 -0
- data/app/transformers/riiif/kakadu_transformer.rb +39 -0
- data/lib/riiif.rb +4 -7
- data/lib/riiif/engine.rb +4 -3
- data/lib/riiif/version.rb +1 -1
- data/riiif.gemspec +1 -1
- data/spec/models/riiif/image_spec.rb +6 -3
- data/spec/models/riiif/transformation_spec.rb +42 -0
- data/spec/services/riiif/imagemagick_command_factory_spec.rb +6 -4
- data/spec/services/riiif/kakadu_command_factory_spec.rb +85 -0
- data/spec/services/riiif/region/absolute_spec.rb +17 -0
- data/spec/services/riiif/size/absolute_spec.rb +17 -0
- data/spec/services/riiif/size/height_spec.rb +13 -0
- data/spec/services/riiif/size/width_spec.rb +13 -0
- data/spec/transformers/riiif/kakadu_transformer_spec.rb +143 -0
- metadata +45 -22
- data/app/services/riiif/region/imagemagick/absolute_decoder.rb +0 -21
- data/app/services/riiif/region/imagemagick/full_decoder.rb +0 -14
- data/app/services/riiif/region/imagemagick/percentage_decoder.rb +0 -33
- data/app/services/riiif/region/imagemagick/square_decoder.rb +0 -25
- data/app/services/riiif/size/imagemagick/absolute_decoder.rb +0 -20
- data/app/services/riiif/size/imagemagick/best_fit_decoder.rb +0 -19
- data/app/services/riiif/size/imagemagick/full_decoder.rb +0 -14
- data/app/services/riiif/size/imagemagick/height_decoder.rb +0 -19
- data/app/services/riiif/size/imagemagick/percent_decoder.rb +0 -19
- data/app/services/riiif/size/imagemagick/width_decoder.rb +0 -19
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Riiif
|
4
|
+
# Builds a command to run a transformation using Kakadu
|
5
|
+
class KakaduCommandFactory
|
6
|
+
class_attribute :external_command
|
7
|
+
self.external_command = 'kdu_expand'
|
8
|
+
|
9
|
+
# A helper method to instantiate and invoke build
|
10
|
+
# @param [String] path the location of the file
|
11
|
+
# @param info [ImageInformation] information about the source
|
12
|
+
# @param [Transformation] transformation
|
13
|
+
def initialize(path, info, transformation)
|
14
|
+
@path = path
|
15
|
+
@info = info
|
16
|
+
@transformation = transformation
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :path, :info, :transformation
|
20
|
+
|
21
|
+
# @param tmp_file [String] the path to the temporary file
|
22
|
+
# @return [String] a command for running kdu_expand to produce the requested output
|
23
|
+
def command(tmp_file)
|
24
|
+
[external_command, quiet, input, threads, region, reduce, output(tmp_file)].join
|
25
|
+
end
|
26
|
+
|
27
|
+
def reduction_factor
|
28
|
+
@reduction_factor ||= transformation.size.reduction_factor
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def input
|
34
|
+
" -i #{path}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def output(output_filename)
|
38
|
+
" -o #{output_filename}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def threads
|
42
|
+
' -num_threads 4'
|
43
|
+
end
|
44
|
+
|
45
|
+
def quiet
|
46
|
+
' -quiet'
|
47
|
+
end
|
48
|
+
|
49
|
+
def region
|
50
|
+
region_arg = transformation.crop.to_kakadu
|
51
|
+
" -region \"#{region_arg}\"" if region_arg
|
52
|
+
end
|
53
|
+
|
54
|
+
# kdu_expand is not capable of arbitrary scaling, but it does
|
55
|
+
# offer a -reduce argument which is capable of downscaling by
|
56
|
+
# factors of 2, significantly speeding decompression. We can
|
57
|
+
# use it if either the percent is <=50, or the height/width
|
58
|
+
# are <=50% of full size.
|
59
|
+
def reduce
|
60
|
+
" -reduce #{reduction_factor}" if reduction_factor && reduction_factor != 0
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
File without changes
|
@@ -52,14 +52,14 @@ module Riiif
|
|
52
52
|
|
53
53
|
def decode_region(region)
|
54
54
|
if region.nil? || region == 'full'
|
55
|
-
Riiif::Region::
|
55
|
+
Riiif::Region::Full.new(image_info)
|
56
56
|
elsif md = /^pct:(\d+),(\d+),(\d+),(\d+)$/.match(region)
|
57
|
-
Riiif::Region::
|
58
|
-
.new(image_info, md[1], md[2], md[3], md[4])
|
57
|
+
Riiif::Region::Percentage
|
58
|
+
.new(image_info, md[1], md[2], md[3], md[4])
|
59
59
|
elsif md = /^(\d+),(\d+),(\d+),(\d+)$/.match(region)
|
60
|
-
Riiif::Region::
|
60
|
+
Riiif::Region::Absolute.new(image_info, md[1], md[2], md[3], md[4])
|
61
61
|
elsif region == 'square'
|
62
|
-
Riiif::Region::
|
62
|
+
Riiif::Region::Square.new(image_info)
|
63
63
|
else
|
64
64
|
raise InvalidAttributeError, "Invalid region: #{region}"
|
65
65
|
end
|
@@ -68,17 +68,17 @@ module Riiif
|
|
68
68
|
# rubocop:disable Metrics/PerceivedComplexity
|
69
69
|
def decode_size(size)
|
70
70
|
if size.nil? || size == 'full'
|
71
|
-
Riiif::Size::
|
71
|
+
Riiif::Size::Full.new
|
72
72
|
elsif md = /^,(\d+)$/.match(size)
|
73
|
-
Riiif::Size::
|
73
|
+
Riiif::Size::Height.new(image_info, md[1])
|
74
74
|
elsif md = /^(\d+),$/.match(size)
|
75
|
-
Riiif::Size::
|
75
|
+
Riiif::Size::Width.new(image_info, md[1])
|
76
76
|
elsif md = /^pct:(\d+(.\d+)?)$/.match(size)
|
77
|
-
Riiif::Size::
|
77
|
+
Riiif::Size::Percent.new(image_info, md[1])
|
78
78
|
elsif md = /^(\d+),(\d+)$/.match(size)
|
79
|
-
Riiif::Size::
|
79
|
+
Riiif::Size::Absolute.new(image_info, md[1], md[2])
|
80
80
|
elsif md = /^!(\d+),(\d+)$/.match(size)
|
81
|
-
Riiif::Size::
|
81
|
+
Riiif::Size::BestFit.new(image_info, md[1], md[2])
|
82
82
|
else
|
83
83
|
raise InvalidAttributeError, "Invalid size: #{size}"
|
84
84
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Riiif
|
2
|
+
module Region
|
3
|
+
# Represents an absolute specified region
|
4
|
+
class Absolute < Crop
|
5
|
+
# TODO: only kakadu needs image_info. So there's potenial to optimize by
|
6
|
+
# making image_info a proxy that fetches the info lazily when needed.
|
7
|
+
# @param [ImageInformation] image_info
|
8
|
+
# @param [String] x
|
9
|
+
# @param [String] y
|
10
|
+
# @param [String] width
|
11
|
+
# @param [String] height
|
12
|
+
def initialize(image_info, x, y, width, height)
|
13
|
+
@image_info = image_info
|
14
|
+
@offset_x = x.to_i
|
15
|
+
@offset_y = y.to_i
|
16
|
+
@width = width.to_i
|
17
|
+
@height = height.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :width, :height
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Riiif
|
2
|
+
module Region
|
3
|
+
# Represents the image or region requested at its full size.
|
4
|
+
# This is a nil crop operation.
|
5
|
+
class Full < Crop
|
6
|
+
def initialize(image_info)
|
7
|
+
@image_info = image_info
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [NilClass] a region for imagemagick to decode
|
11
|
+
# the nil implies no cropping needed
|
12
|
+
def to_imagemagick
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [NilClass] a region for kakadu to decode
|
17
|
+
# the nil implies no cropping needed
|
18
|
+
def to_kakadu
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Riiif
|
2
|
+
module Region
|
3
|
+
# represents request cooridnates specified as percentage
|
4
|
+
class Percentage < Crop
|
5
|
+
def initialize(image_info, x, y, width, height)
|
6
|
+
@image_info = image_info
|
7
|
+
@x_pct = x
|
8
|
+
@y_pct = y
|
9
|
+
@width_pct = width
|
10
|
+
@height_pct = height
|
11
|
+
end
|
12
|
+
|
13
|
+
# From the Imagemagick docs:
|
14
|
+
# The percentage symbol '%' can appear anywhere in a argument, and if
|
15
|
+
# given will refer to both width and height numbers. It is a flag that
|
16
|
+
# just declares that the 'image size' parts are a percentage fraction
|
17
|
+
# of the images virtual canvas or page size. Offsets are always given
|
18
|
+
# in pixels.
|
19
|
+
# @return [String] a region for imagemagick to decode
|
20
|
+
# (appropriate for passing to the -crop parameter)
|
21
|
+
def to_imagemagick
|
22
|
+
"#{@width_pct}%x#{@height_pct}+#{offset_x}+#{offset_y}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def maintain_aspect_ratio?
|
26
|
+
@width_pct == @height_pct
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @param [String] n a percentage to convert
|
32
|
+
# @return [Float]
|
33
|
+
def percentage_to_fraction(n)
|
34
|
+
Integer(n).to_f / 100
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Integer]
|
38
|
+
def offset_x
|
39
|
+
(@image_info.width * percentage_to_fraction(@x_pct)).round
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Integer]
|
43
|
+
def offset_y
|
44
|
+
(@image_info.height * percentage_to_fraction(@y_pct)).round
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Float]
|
48
|
+
def decimal_height
|
49
|
+
percentage_to_fraction(@height_pct)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Float]
|
53
|
+
def decimal_width
|
54
|
+
percentage_to_fraction(@width_pct)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Float]
|
58
|
+
def decimal_offset_y
|
59
|
+
percentage_to_fraction(@y_pct)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Float]
|
63
|
+
def decimal_offset_x
|
64
|
+
percentage_to_fraction(@x_pct)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Riiif
|
2
|
+
module Region
|
3
|
+
# Represents requested square cooridnates
|
4
|
+
class Square < Crop
|
5
|
+
def initialize(image_info)
|
6
|
+
@image_info = image_info
|
7
|
+
@min, @max = [@image_info.width, @image_info.height].minmax
|
8
|
+
@offset = (@max - @min) / 2
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [String] a square region for imagemagick to decode
|
12
|
+
# (appropriate for passing to the -crop parameter)
|
13
|
+
def to_imagemagick
|
14
|
+
if @image_info.height >= @image_info.width
|
15
|
+
"#{height}x#{width}+0+#{@offset}"
|
16
|
+
else
|
17
|
+
"#{height}x#{width}+#{@offset}+0"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String] a region for kakadu to decode
|
22
|
+
# (appropriate for passing to the -region parameter)
|
23
|
+
def to_kakadu
|
24
|
+
# (top, left, height, width)
|
25
|
+
if @image_info.height >= @image_info.width
|
26
|
+
# Portrait
|
27
|
+
"\{#{decimal_height(@offset)},0\}," \
|
28
|
+
"\{#{decimal_height(height)},#{decimal_width(height)}\}"
|
29
|
+
else
|
30
|
+
# Landscape
|
31
|
+
"\{0,#{decimal_width(@offset)}\}," \
|
32
|
+
"\{#{decimal_height(width)},#{decimal_width(width)}\}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def height
|
37
|
+
@min
|
38
|
+
end
|
39
|
+
|
40
|
+
def width
|
41
|
+
@min
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Riiif
|
2
|
+
# Represents a resize operation
|
3
|
+
class Resize
|
4
|
+
attr_reader :image_info
|
5
|
+
|
6
|
+
# @return [Integer] the height in pixels
|
7
|
+
def height
|
8
|
+
image_info.height
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [Integer] the width in pixels
|
12
|
+
def width
|
13
|
+
image_info.width
|
14
|
+
end
|
15
|
+
|
16
|
+
# Should we reduce this image with KDU?
|
17
|
+
def reduce?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
# This is used for a second resize by imagemagick after resizing
|
22
|
+
# by kdu.
|
23
|
+
# No need to scale most resize operations (only percent)
|
24
|
+
# @param [Integer] factor to scale by
|
25
|
+
# @return [Absolute] a copy of self if factor is zero.
|
26
|
+
def reduce(_factor)
|
27
|
+
dup
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Integer] the reduction factor for this operation
|
31
|
+
def reduction_factor(max_factor = 5)
|
32
|
+
return nil unless reduce?
|
33
|
+
scale = [width.to_f / image_info.width,
|
34
|
+
height.to_f / image_info.height].min
|
35
|
+
factor = 0
|
36
|
+
raise "I don't know how to scale to #{scale}" if scale > 1
|
37
|
+
next_pct = 0.5
|
38
|
+
while scale <= next_pct && factor < max_factor
|
39
|
+
next_pct /= 2.0
|
40
|
+
factor += 1
|
41
|
+
end
|
42
|
+
factor
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Riiif
|
2
|
+
module Size
|
3
|
+
# The width and height of the returned image are exactly w and h.
|
4
|
+
# The aspect ratio of the returned image may be different than the extracted
|
5
|
+
# region, resulting in a distorted image.
|
6
|
+
class Absolute < Resize
|
7
|
+
# @param [ImageInformation] info
|
8
|
+
# @param [String] width
|
9
|
+
# @param [String] height
|
10
|
+
def initialize(info, width, height)
|
11
|
+
@image_info = info
|
12
|
+
@width = width.to_i
|
13
|
+
@height = height.to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String] a resize directive for imagemagick to use
|
17
|
+
def to_imagemagick
|
18
|
+
"#{@width}x#{@height}!"
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :height, :width
|
22
|
+
|
23
|
+
# Reduce this if the aspect ratio of the image is maintained.
|
24
|
+
def reduce?
|
25
|
+
in_delta?(image_info.aspect_ratio, aspect_ratio, 0.001)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def aspect_ratio
|
31
|
+
width.to_f / height
|
32
|
+
end
|
33
|
+
|
34
|
+
def in_delta?(x1, x2, delta)
|
35
|
+
(x1 - x2).abs <= delta
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Riiif
|
2
|
+
module Size
|
3
|
+
# The image content is scaled for the best fit such that the resulting width and
|
4
|
+
# height are less than or equal to the requested width and height.
|
5
|
+
class BestFit < Resize
|
6
|
+
def initialize(info, width, height)
|
7
|
+
@image_info = info
|
8
|
+
@width = width
|
9
|
+
@height = height
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String] a resize directive for imagemagick to use
|
13
|
+
def to_imagemagick
|
14
|
+
"#{@width}x#{@height}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Riiif
|
2
|
+
module Size
|
3
|
+
# represents requested full size
|
4
|
+
class Full < Resize
|
5
|
+
# @return [NilClass] a size for imagemagick to decode
|
6
|
+
# the nil implies no resizing needed
|
7
|
+
def to_imagemagick
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
# Should we reduce this image?
|
12
|
+
def reduce?
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Riiif
|
2
|
+
module Size
|
3
|
+
# The image or region should be scaled so that its height is exactly equal
|
4
|
+
# to the provided parameter, and the width will be a calculated value that
|
5
|
+
# maintains the aspect ratio of the extracted region
|
6
|
+
class Height < Resize
|
7
|
+
def initialize(info, height)
|
8
|
+
@image_info = info
|
9
|
+
@height = height.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String] a resize directive for imagemagick to use
|
13
|
+
def to_imagemagick
|
14
|
+
"x#{@height}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def width
|
18
|
+
height * image_info.width / image_info.height
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :height
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|