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.
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