riiif 1.7.1 → 2.0.0.beta1
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 +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
|