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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +15 -41
  3. data/Gemfile +1 -37
  4. data/app/models/riiif/file.rb +19 -12
  5. data/app/models/riiif/image.rb +13 -12
  6. data/app/models/riiif/image_information.rb +4 -0
  7. data/app/models/riiif/transformation.rb +35 -0
  8. data/{lib → app/resolvers}/riiif/abstract_file_system_resolver.rb +0 -0
  9. data/{lib → app/resolvers}/riiif/akubra_system_file_resolver.rb +0 -0
  10. data/{lib → app/resolvers}/riiif/file_system_file_resolver.rb +0 -0
  11. data/{lib → app/resolvers}/riiif/http_file_resolver.rb +0 -0
  12. data/app/services/riiif/command_runner.rb +2 -0
  13. data/app/services/riiif/crop.rb +54 -0
  14. data/app/services/riiif/imagemagick_command_factory.rb +20 -19
  15. data/app/services/riiif/imagemagick_transformer.rb +8 -0
  16. data/app/services/riiif/kakadu_command_factory.rb +63 -0
  17. data/app/services/riiif/link_name_service.rb +8 -0
  18. data/{lib → app/services}/riiif/nil_authorization_service.rb +0 -0
  19. data/app/services/riiif/option_decoder.rb +11 -11
  20. data/app/services/riiif/region/absolute.rb +23 -0
  21. data/app/services/riiif/region/full.rb +23 -0
  22. data/app/services/riiif/region/percentage.rb +68 -0
  23. data/app/services/riiif/region/square.rb +45 -0
  24. data/app/services/riiif/resize.rb +45 -0
  25. data/app/services/riiif/size/absolute.rb +39 -0
  26. data/app/services/riiif/size/best_fit.rb +18 -0
  27. data/app/services/riiif/size/full.rb +17 -0
  28. data/app/services/riiif/size/height.rb +24 -0
  29. data/app/services/riiif/size/percent.rb +44 -0
  30. data/app/services/riiif/size/width.rb +24 -0
  31. data/app/transformers/riiif/abstract_transformer.rb +30 -0
  32. data/app/transformers/riiif/imagemagick_transformer.rb +8 -0
  33. data/app/transformers/riiif/kakadu_transformer.rb +39 -0
  34. data/lib/riiif.rb +4 -7
  35. data/lib/riiif/engine.rb +4 -3
  36. data/lib/riiif/version.rb +1 -1
  37. data/riiif.gemspec +1 -1
  38. data/spec/models/riiif/image_spec.rb +6 -3
  39. data/spec/models/riiif/transformation_spec.rb +42 -0
  40. data/spec/services/riiif/imagemagick_command_factory_spec.rb +6 -4
  41. data/spec/services/riiif/kakadu_command_factory_spec.rb +85 -0
  42. data/spec/services/riiif/region/absolute_spec.rb +17 -0
  43. data/spec/services/riiif/size/absolute_spec.rb +17 -0
  44. data/spec/services/riiif/size/height_spec.rb +13 -0
  45. data/spec/services/riiif/size/width_spec.rb +13 -0
  46. data/spec/transformers/riiif/kakadu_transformer_spec.rb +143 -0
  47. metadata +45 -22
  48. data/app/services/riiif/region/imagemagick/absolute_decoder.rb +0 -21
  49. data/app/services/riiif/region/imagemagick/full_decoder.rb +0 -14
  50. data/app/services/riiif/region/imagemagick/percentage_decoder.rb +0 -33
  51. data/app/services/riiif/region/imagemagick/square_decoder.rb +0 -25
  52. data/app/services/riiif/size/imagemagick/absolute_decoder.rb +0 -20
  53. data/app/services/riiif/size/imagemagick/best_fit_decoder.rb +0 -19
  54. data/app/services/riiif/size/imagemagick/full_decoder.rb +0 -14
  55. data/app/services/riiif/size/imagemagick/height_decoder.rb +0 -19
  56. data/app/services/riiif/size/imagemagick/percent_decoder.rb +0 -19
  57. data/app/services/riiif/size/imagemagick/width_decoder.rb +0 -19
@@ -0,0 +1,8 @@
1
+ module Riiif
2
+ # Transforms an image using Imagemagick
3
+ class ImagemagickTransformer < AbstractTransformer
4
+ def command_factory
5
+ ImagemagickCommandFactory
6
+ end
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,8 @@
1
+ module Riiif
2
+ # Creates names for a temporary file
3
+ class LinkNameService
4
+ def self.create
5
+ ::File.join(Dir.tmpdir, SecureRandom.uuid) + '.bmp'
6
+ end
7
+ end
8
+ end
@@ -52,14 +52,14 @@ module Riiif
52
52
 
53
53
  def decode_region(region)
54
54
  if region.nil? || region == 'full'
55
- Riiif::Region::Imagemagick::FullDecoder.new.decode
55
+ Riiif::Region::Full.new(image_info)
56
56
  elsif md = /^pct:(\d+),(\d+),(\d+),(\d+)$/.match(region)
57
- Riiif::Region::Imagemagick::PercentageDecoder
58
- .new(image_info, md[1], md[2], md[3], md[4]).decode
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::Imagemagick::AbsoluteDecoder.new(md[1], md[2], md[3], md[4]).decode
60
+ Riiif::Region::Absolute.new(image_info, md[1], md[2], md[3], md[4])
61
61
  elsif region == 'square'
62
- Riiif::Region::Imagemagick::SquareDecoder.new(image_info).decode
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::Imagemagick::FullDecoder.new.decode
71
+ Riiif::Size::Full.new
72
72
  elsif md = /^,(\d+)$/.match(size)
73
- Riiif::Size::Imagemagick::HeightDecoder.new(md[1]).decode
73
+ Riiif::Size::Height.new(image_info, md[1])
74
74
  elsif md = /^(\d+),$/.match(size)
75
- Riiif::Size::Imagemagick::WidthDecoder.new(md[1]).decode
75
+ Riiif::Size::Width.new(image_info, md[1])
76
76
  elsif md = /^pct:(\d+(.\d+)?)$/.match(size)
77
- Riiif::Size::Imagemagick::PercentDecoder.new(md[1]).decode
77
+ Riiif::Size::Percent.new(image_info, md[1])
78
78
  elsif md = /^(\d+),(\d+)$/.match(size)
79
- Riiif::Size::Imagemagick::AbsoluteDecoder.new(md[1], md[2]).decode
79
+ Riiif::Size::Absolute.new(image_info, md[1], md[2])
80
80
  elsif md = /^!(\d+),(\d+)$/.match(size)
81
- Riiif::Size::Imagemagick::BestFitDecoder.new(md[1], md[2]).decode
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