imogen 0.3.1 → 0.4.0
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/lib/imogen/iiif/tiles.rb +145 -22
- data/lib/imogen/iiif.rb +3 -1
- data/lib/imogen.rb +13 -22
- data/spec/integration/imogen_iiif_spec.rb +2 -1
- data/spec/integration/imogen_iiif_tiles_spec.rb +94 -0
- data/spec/integration/imogen_spec.rb +9 -11
- data/spec/unit/imogen_iiif_tiles_openseadragon_expectations_spec.rb +106 -0
- data/spec/unit/imogen_iiif_tiles_spec.rb +67 -47
- metadata +5 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 032e49d9a8602410ab24b57efc8bd3de1cd523ad41e217bd76affcca385a08aa
         | 
| 4 | 
            +
              data.tar.gz: 2ae67a08b37695662dd21af0eedc7c7449423fde11ca20cd02c2f66e00ade5f4
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 9a5499840827444cea022ef95ca205f52131084938a6417441eb73898961b726b02229d6f2e14964a77bf4fa24a537ae7b5c582fec1d7bcaf72f556e789dbd23
         | 
| 7 | 
            +
              data.tar.gz: 6f53cf3ee9a63cd4906f2f09ede862f31440e573ea0d7033ce37c50d0d661d733dc7cb355e2a137e94f6a3eeb5e91904d2c37c7059039d145722a0b976b656cd
         | 
    
        data/lib/imogen/iiif/tiles.rb
    CHANGED
    
    | @@ -2,37 +2,160 @@ require 'imogen/zoomable' | |
| 2 2 | 
             
            module Imogen
         | 
| 3 3 | 
             
              module Iiif
         | 
| 4 4 | 
             
                module Tiles
         | 
| 5 | 
            -
                  def self. | 
| 6 | 
            -
                    Imogen::Zoomable. | 
| 5 | 
            +
                  def self.scale_factors_for(full_image_width, full_image_height, tile_size)
         | 
| 6 | 
            +
                    largest_scale_factor = Imogen::Zoomable.max_levels_for(full_image_width, full_image_height, tile_size)
         | 
| 7 | 
            +
                    largest_scale_factor -= Math.log2(tile_size / 2) # remove scales smaller than tile size
         | 
| 8 | 
            +
                    (0..(largest_scale_factor.to_i)).map { |exp| 2.pow(exp) }
         | 
| 7 9 | 
             
                  end
         | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 10 | 
            +
             | 
| 11 | 
            +
                  # Yields many times, for each set of iiif tile raster opts for the given image.
         | 
| 12 | 
            +
                  # @param img [Image] The image to be analyzed.
         | 
| 13 | 
            +
                  # @param dest_dir [String] The target output directory for the tile subdirectory hierarchy.
         | 
| 14 | 
            +
                  # @param format [String] Tile format
         | 
| 15 | 
            +
                  # @param tile_size [Integer] Tile size
         | 
| 16 | 
            +
                  # @param quality [String] IIIF quality value (e.g. 'color', 'default')
         | 
| 17 | 
            +
                  # @yield [image, suggested_dest_path_for_tile, format, raster_opts] Image and tile generation info
         | 
| 18 | 
            +
                  def self.for(img, dest_dir, format = :jpg, tile_size = 128, quality = 'default')
         | 
| 19 | 
            +
                    format = :jpg if format.to_s == 'jpeg'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    width = img.width
         | 
| 22 | 
            +
                    height = img.height
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    # For this implementation, we will only support square tiles (same width and height)
         | 
| 25 | 
            +
                    tile_width = tile_size
         | 
| 26 | 
            +
                    tile_height = tile_size
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    # If the original image dimensions are smaller than the tile_size,
         | 
| 29 | 
            +
                    # generate a tile for region 'full' and size 'full'.
         | 
| 30 | 
            +
                    if width < tile_size && height < tile_size
         | 
| 31 | 
            +
                      raster_opts = {
         | 
| 32 | 
            +
                        region: 'full',
         | 
| 33 | 
            +
                        size: 'full',
         | 
| 34 | 
            +
                        rotation: 0,
         | 
| 35 | 
            +
                        quality: quality,
         | 
| 36 | 
            +
                        format: format
         | 
| 37 | 
            +
                      }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      dest_path = File.join(
         | 
| 40 | 
            +
                        dest_dir,
         | 
| 41 | 
            +
                        raster_opts[:region],
         | 
| 42 | 
            +
                        raster_opts[:size],
         | 
| 43 | 
            +
                        raster_opts[:rotation].to_s,
         | 
| 44 | 
            +
                        "#{raster_opts[:quality]}.#{Imogen::Iiif::FORMATS[raster_opts[:format]]}"
         | 
| 45 | 
            +
                      )
         | 
| 46 | 
            +
                      yield(img, dest_path, raster_opts['format'], Imogen::Iiif.path_to_opts(dest_path, dest_dir))
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
             | 
| 50 | 
            +
                    # NOTE: Algorithm below is based on: https://iiif.io/api/image/2.1/#a-implementation-notes
         | 
| 51 | 
            +
                    self.scale_factors_for(width, height, tile_size).each do |scale_factor|
         | 
| 52 | 
            +
                      scale_factor_as_float = scale_factor.to_f
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                      col = 0
         | 
| 55 | 
            +
                      x = 0
         | 
| 19 56 | 
             
                      while x < width
         | 
| 20 | 
            -
                         | 
| 57 | 
            +
                        row = 0
         | 
| 58 | 
            +
                        y = 0
         | 
| 21 59 | 
             
                        while y < height
         | 
| 22 | 
            -
                          region  | 
| 23 | 
            -
                           | 
| 24 | 
            -
                           | 
| 25 | 
            -
                           | 
| 26 | 
            -
             | 
| 60 | 
            +
                          # Calculate region parameters /xr,yr,wr,hr/
         | 
| 61 | 
            +
                          xr = col * tile_width * scale_factor_as_float
         | 
| 62 | 
            +
                          yr = row * tile_height * scale_factor_as_float
         | 
| 63 | 
            +
                          wr = tile_width * scale_factor_as_float
         | 
| 64 | 
            +
                          if xr + wr > width
         | 
| 65 | 
            +
                            wr = width - xr
         | 
| 66 | 
            +
                          end
         | 
| 67 | 
            +
                          hr = tile_height * scale_factor_as_float
         | 
| 68 | 
            +
                          if yr + hr > height
         | 
| 69 | 
            +
                            hr = height - yr
         | 
| 70 | 
            +
                          end
         | 
| 71 | 
            +
                          # Calculate size parameters /ws,hs/
         | 
| 72 | 
            +
                          ws = tile_width
         | 
| 73 | 
            +
                          if xr + tile_width * scale_factor_as_float > width
         | 
| 74 | 
            +
                            ws = (width - xr + scale_factor_as_float - 1) / scale_factor_as_float  # +scale_factor_as_floatZ-1 in numerator to round up
         | 
| 75 | 
            +
                          end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                          hs = tile_height
         | 
| 78 | 
            +
                          if yr + tile_height * scale_factor_as_float > height
         | 
| 79 | 
            +
                            hs = (height - yr + scale_factor_as_float - 1) / scale_factor_as_float
         | 
| 27 80 | 
             
                          end
         | 
| 28 | 
            -
             | 
| 81 | 
            +
             | 
| 82 | 
            +
                          # If the region width (wr) or region height (hr) go negative, we've gone too far. Break!
         | 
| 83 | 
            +
                          break if wr <= 0 || hr <= 0
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                          xr = xr.floor
         | 
| 86 | 
            +
                          yr = yr.floor
         | 
| 87 | 
            +
                          wr = wr.floor
         | 
| 88 | 
            +
                          hr = hr.floor
         | 
| 89 | 
            +
                          ws = ws.floor
         | 
| 90 | 
            +
                          hs = hs.floor
         | 
| 91 | 
            +
                          region = "#{xr},#{yr},#{wr},#{hr}"
         | 
| 92 | 
            +
                          size = "#{ws},"
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                          # When tile_width and tile_height are the same, OpenSeadragon only specifies
         | 
| 95 | 
            +
                          # the width for the size param in image slice URLs, so we will generally not
         | 
| 96 | 
            +
                          # include the height in the size param string.
         | 
| 97 | 
            +
                          size += "#{hs}" if tile_width != tile_height
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                          # Need to do this correction for OpenSeadragon Compatibility, since it asks for "full" in this case.
         | 
| 100 | 
            +
                          region = 'full' if region == "0,0,#{width},#{height}"
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                          raster_opts = {
         | 
| 103 | 
            +
                            region: region,
         | 
| 104 | 
            +
                            size: size,
         | 
| 105 | 
            +
                            rotation: 0,
         | 
| 106 | 
            +
                            quality: quality,
         | 
| 107 | 
            +
                            format: format
         | 
| 108 | 
            +
                          }
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                          dest_path = File.join(
         | 
| 111 | 
            +
                            dest_dir,
         | 
| 112 | 
            +
                            raster_opts[:region],
         | 
| 113 | 
            +
                            raster_opts[:size],
         | 
| 114 | 
            +
                            raster_opts[:rotation].to_s,
         | 
| 115 | 
            +
                            "#{raster_opts[:quality]}.#{Imogen::Iiif::FORMATS[raster_opts[:format]]}"
         | 
| 116 | 
            +
                          )
         | 
| 117 | 
            +
                          yield(img, dest_path, raster_opts['format'], Imogen::Iiif.path_to_opts(dest_path, dest_dir))
         | 
| 118 | 
            +
             | 
| 29 119 | 
             
                          row += 1
         | 
| 120 | 
            +
                          y += tile_height
         | 
| 30 121 | 
             
                        end
         | 
| 31 | 
            -
                        x += region_size
         | 
| 32 122 | 
             
                        col += 1
         | 
| 123 | 
            +
                        x += tile_width
         | 
| 33 124 | 
             
                      end
         | 
| 34 125 | 
             
                    end
         | 
| 35 126 | 
             
                  end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                  # This method should NOT be used right now because it's missing some tiles that we rely on.
         | 
| 129 | 
            +
                  # This method is just here as a partial vips-dzsave-based implementation that we may want to
         | 
| 130 | 
            +
                  # build off in a fututure release.
         | 
| 131 | 
            +
                  # The Imogen::Iiif::Tiles.for method should be used instead, since it generates all of the
         | 
| 132 | 
            +
                  # expected tiles.
         | 
| 133 | 
            +
                  # The issue with this method may be related to this:
         | 
| 134 | 
            +
                  # https://github.com/libvips/libvips/discussions/2036
         | 
| 135 | 
            +
                  def self.generate_with_vips_dzsave(img, output_dir, format: :jpeg, tile_size: 128, tile_filename_without_extension: 'default')
         | 
| 136 | 
            +
                    warn "Warning: The generate_with_vips_dzsave is only partially functional and should not "\
         | 
| 137 | 
            +
                         "be used to generate tiles yet.  If you use this method, some IIIF tiles will be missing."
         | 
| 138 | 
            +
                    format = :jpg if format == :jpeg
         | 
| 139 | 
            +
                    format = format.to_sym
         | 
| 140 | 
            +
                    img.dzsave(
         | 
| 141 | 
            +
                      output_dir,
         | 
| 142 | 
            +
                      layout: 'iiif',
         | 
| 143 | 
            +
                      suffix: ".tmp.#{format}",
         | 
| 144 | 
            +
                      overlap: 0,
         | 
| 145 | 
            +
                      tile_size: tile_size
         | 
| 146 | 
            +
                    )
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                    # Update tile names with desired value
         | 
| 149 | 
            +
                    Dir[File.join(output_dir, "**/*.tmp.#{format}")].each do |file_path|
         | 
| 150 | 
            +
                      new_name = File.join(File.dirname(file_path), "#{tile_filename_without_extension}.#{format}")
         | 
| 151 | 
            +
                      File.rename(file_path, new_name)
         | 
| 152 | 
            +
                    end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                    # Clean up unused additional dzsave files
         | 
| 155 | 
            +
                    ['info.json', 'vips-properties.xml'].each do |unnecessary_file_name|
         | 
| 156 | 
            +
                      File.delete(file_to_delete)
         | 
| 157 | 
            +
                    end
         | 
| 158 | 
            +
                  end
         | 
| 36 159 | 
             
                end
         | 
| 37 160 | 
             
              end
         | 
| 38 | 
            -
            end
         | 
| 161 | 
            +
            end
         | 
    
        data/lib/imogen/iiif.rb
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            module Imogen
         | 
| 2 2 | 
             
              module Iiif
         | 
| 3 | 
            -
                class BadRequest <  | 
| 3 | 
            +
                class BadRequest < StandardError; end
         | 
| 4 4 | 
             
                class Transform
         | 
| 5 5 | 
             
                  def initialize(src)
         | 
| 6 6 | 
             
                    @width = 0
         | 
| @@ -45,6 +45,7 @@ module Imogen | |
| 45 45 |  | 
| 46 46 | 
             
                FORMATS = {jpeg: 'jpg', jpg: 'jpg', png: 'png', jp2: 'jp2'}
         | 
| 47 47 | 
             
                EXTENSIONS = {'jpg' => :jpeg, 'png' => :png, 'jp2' => :jp2}
         | 
| 48 | 
            +
             | 
| 48 49 | 
             
                def self.convert(img, dest_path, format=nil, opts={})
         | 
| 49 50 | 
             
                  format ||= opts.fetch(:format,:jpeg)
         | 
| 50 51 | 
             
                  format = format.to_sym
         | 
| @@ -60,6 +61,7 @@ module Imogen | |
| 60 61 | 
             
                    end
         | 
| 61 62 | 
             
                  end
         | 
| 62 63 | 
             
                end
         | 
| 64 | 
            +
             | 
| 63 65 | 
             
                def self.path_to_opts(path,parent_dir)
         | 
| 64 66 | 
             
                  if parent_dir and path.start_with? parent_dir
         | 
| 65 67 | 
             
                    path = path.sub(parent_dir,'')
         | 
    
        data/lib/imogen.rb
    CHANGED
    
    | @@ -38,34 +38,25 @@ module Imogen | |
| 38 38 |  | 
| 39 39 | 
             
              # @param [String] src_path The local file path to the image.
         | 
| 40 40 | 
             
              # @param [Hash] opts The options to use when opening an image.
         | 
| 41 | 
            -
              # @option opts [Boolean] : | 
| 41 | 
            +
              # @option opts [Boolean] :revalidate (Requires libvips > 8.15) When true, will force the underlying
         | 
| 42 | 
            +
              #                                    Vips library to reload the source file instead of using cached
         | 
| 43 | 
            +
              #                                    data from an earlier read.  This is useful if the source
         | 
| 44 | 
            +
              #                                    file was recently recreated.
         | 
| 42 45 | 
             
              def self.image(src_path, opts = {})
         | 
| 43 | 
            -
                 | 
| 44 | 
            -
             | 
| 45 | 
            -
                 | 
| 46 | 
            -
             | 
| 47 | 
            -
                 | 
| 48 | 
            -
                Vips::Image.new_from_file(src_path)
         | 
| 46 | 
            +
                if opts.empty?
         | 
| 47 | 
            +
                  Vips::Image.new_from_file(src_path)
         | 
| 48 | 
            +
                else
         | 
| 49 | 
            +
                  Vips::Image.new_from_file(src_path, **opts)
         | 
| 50 | 
            +
                end
         | 
| 49 51 | 
             
              end
         | 
| 50 52 |  | 
| 51 53 | 
             
              # @param [String] src_path The local file path to the image.
         | 
| 52 54 | 
             
              # @param [Hash] opts The options to use when opening an image.
         | 
| 53 | 
            -
              # @option opts [Boolean] : | 
| 55 | 
            +
              # @option opts [Boolean] :revalidate (Requires libvips > 8.15) When true, will force the underlying
         | 
| 56 | 
            +
              #                                    Vips library to reload the source file instead of using cached
         | 
| 57 | 
            +
              #                                    data from an earlier read.  This is useful if the source
         | 
| 58 | 
            +
              #                                    file was recently recreated.
         | 
| 54 59 | 
             
              def self.with_image(src_path, opts = {}, &block)
         | 
| 55 60 | 
             
                block.yield(image(src_path, opts))
         | 
| 56 61 | 
             
              end
         | 
| 57 | 
            -
             | 
| 58 | 
            -
              # TODO: The clear_vips_cache_mem method can be removed once these two tickets are addressed:
         | 
| 59 | 
            -
              # 1) https://github.com/libvips/ruby-vips/issues/360
         | 
| 60 | 
            -
              # 2) https://github.com/libvips/libvips/pull/3370
         | 
| 61 | 
            -
              def self.clear_vips_cache_mem
         | 
| 62 | 
            -
                # store original max because we'll restore it later
         | 
| 63 | 
            -
                original_max_value = vips_cache_get_max_mem
         | 
| 64 | 
            -
             | 
| 65 | 
            -
                # Drop max mem to 0, which also internally triggers a trim operation that clears out old cache entries
         | 
| 66 | 
            -
                vips_cache_set_max_mem(0)
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                # Restore original value to support future caching operations
         | 
| 69 | 
            -
                vips_cache_set_max_mem(original_max_value)
         | 
| 70 | 
            -
              end
         | 
| 71 62 | 
             
            end
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            require 'imogen'
         | 
| 2 2 | 
             
            require 'tmpdir'
         | 
| 3 3 |  | 
| 4 | 
            -
            describe Imogen:: | 
| 4 | 
            +
            describe Imogen::Iiif, vips: true do
         | 
| 5 5 | 
             
              describe "#convert" do
         | 
| 6 6 | 
             
                let(:output_file) { Dir.tmpdir + '/test-imogen-convert.jpg' }
         | 
| 7 7 | 
             
                it "should successfully convert the image" do
         | 
| @@ -16,3 +16,4 @@ describe Imogen::AutoCrop, vips: true do | |
| 16 16 | 
             
                end
         | 
| 17 17 | 
             
              end
         | 
| 18 18 | 
             
            end
         | 
| 19 | 
            +
             | 
| @@ -0,0 +1,94 @@ | |
| 1 | 
            +
            require 'imogen'
         | 
| 2 | 
            +
            require 'tmpdir'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Imogen::Iiif::Tiles, vips: true do
         | 
| 5 | 
            +
              let(:source_image) { fixture('sample.jpg').path } # sample.jpg is 1920w × 3125h
         | 
| 6 | 
            +
              let(:tile_size) { 512 }
         | 
| 7 | 
            +
              let(:output_dir) { Dir.tmpdir + '/tile-output-dir' }
         | 
| 8 | 
            +
              let(:expected_files) do
         | 
| 9 | 
            +
                [
         | 
| 10 | 
            +
                  '/0,0,512,512/512,/0/default.jpg',
         | 
| 11 | 
            +
                  '/0,512,512,512/512,/0/default.jpg',
         | 
| 12 | 
            +
                  '/0,1024,512,512/512,/0/default.jpg',
         | 
| 13 | 
            +
                  '/0,1536,512,512/512,/0/default.jpg',
         | 
| 14 | 
            +
                  '/0,2048,512,512/512,/0/default.jpg',
         | 
| 15 | 
            +
                  '/0,2560,512,512/512,/0/default.jpg',
         | 
| 16 | 
            +
                  '/0,3072,512,53/512,/0/default.jpg',
         | 
| 17 | 
            +
                  '/512,0,512,512/512,/0/default.jpg',
         | 
| 18 | 
            +
                  '/512,512,512,512/512,/0/default.jpg',
         | 
| 19 | 
            +
                  '/512,1024,512,512/512,/0/default.jpg',
         | 
| 20 | 
            +
                  '/512,1536,512,512/512,/0/default.jpg',
         | 
| 21 | 
            +
                  '/512,2048,512,512/512,/0/default.jpg',
         | 
| 22 | 
            +
                  '/512,2560,512,512/512,/0/default.jpg',
         | 
| 23 | 
            +
                  '/512,3072,512,53/512,/0/default.jpg',
         | 
| 24 | 
            +
                  '/1024,0,512,512/512,/0/default.jpg',
         | 
| 25 | 
            +
                  '/1024,512,512,512/512,/0/default.jpg',
         | 
| 26 | 
            +
                  '/1024,1024,512,512/512,/0/default.jpg',
         | 
| 27 | 
            +
                  '/1024,1536,512,512/512,/0/default.jpg',
         | 
| 28 | 
            +
                  '/1024,2048,512,512/512,/0/default.jpg',
         | 
| 29 | 
            +
                  '/1024,2560,512,512/512,/0/default.jpg',
         | 
| 30 | 
            +
                  '/1024,3072,512,53/512,/0/default.jpg',
         | 
| 31 | 
            +
                  '/1536,0,384,512/384,/0/default.jpg',
         | 
| 32 | 
            +
                  '/1536,512,384,512/384,/0/default.jpg',
         | 
| 33 | 
            +
                  '/1536,1024,384,512/384,/0/default.jpg',
         | 
| 34 | 
            +
                  '/1536,1536,384,512/384,/0/default.jpg',
         | 
| 35 | 
            +
                  '/1536,2048,384,512/384,/0/default.jpg',
         | 
| 36 | 
            +
                  '/1536,2560,384,512/384,/0/default.jpg',
         | 
| 37 | 
            +
                  '/1536,3072,384,53/384,/0/default.jpg',
         | 
| 38 | 
            +
                  '/0,0,1024,1024/512,/0/default.jpg',
         | 
| 39 | 
            +
                  '/0,1024,1024,1024/512,/0/default.jpg',
         | 
| 40 | 
            +
                  '/0,2048,1024,1024/512,/0/default.jpg',
         | 
| 41 | 
            +
                  '/0,3072,1024,53/512,/0/default.jpg',
         | 
| 42 | 
            +
                  '/1024,0,896,1024/448,/0/default.jpg',
         | 
| 43 | 
            +
                  '/1024,1024,896,1024/448,/0/default.jpg',
         | 
| 44 | 
            +
                  '/1024,2048,896,1024/448,/0/default.jpg',
         | 
| 45 | 
            +
                  '/1024,3072,896,53/448,/0/default.jpg',
         | 
| 46 | 
            +
                  '/0,0,1920,2048/480,/0/default.jpg',
         | 
| 47 | 
            +
                  '/0,2048,1920,1077/480,/0/default.jpg',
         | 
| 48 | 
            +
                  '/full/240,/0/default.jpg',
         | 
| 49 | 
            +
                  '/full/120,/0/default.jpg',
         | 
| 50 | 
            +
                ].map { |partial_path| File.join(output_dir, partial_path) }
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              describe "#for" do
         | 
| 54 | 
            +
                it "should successfully generate the expected tile files" do
         | 
| 55 | 
            +
                  Imogen.with_image(source_image) do |image|
         | 
| 56 | 
            +
                    Imogen::Iiif::Tiles.for(image, output_dir, :jpeg, tile_size) do |img, suggested_tile_dest_path, format, opts|
         | 
| 57 | 
            +
                      FileUtils.mkdir_p(File.dirname(suggested_tile_dest_path))
         | 
| 58 | 
            +
                      Imogen::Iiif.convert(img, suggested_tile_dest_path, format, opts)
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                  generated_files = Dir["#{output_dir}/**/*.jpg"]
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  # Check for missing files and extra unexpected files
         | 
| 64 | 
            +
                  missing_files = expected_files.sort - generated_files.sort
         | 
| 65 | 
            +
                  extra_unexepcted_files = generated_files.sort - expected_files.sort
         | 
| 66 | 
            +
                  expect(missing_files).to eq([])
         | 
| 67 | 
            +
                  expect(extra_unexepcted_files).to eq([])
         | 
| 68 | 
            +
                ensure
         | 
| 69 | 
            +
                  FileUtils.rm_rf(output_dir) if File.exist?(output_dir)
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
              describe "#generate_vips_dzsave_tiles" do
         | 
| 74 | 
            +
                pending "should successfully generate the expected tile files" do
         | 
| 75 | 
            +
                  Imogen.with_image(source_image) do |image|
         | 
| 76 | 
            +
                    Imogen::Iiif::Tiles.generate_with_vips_dzsave(image, output_dir, format: 'jpg', tile_size: tile_size)
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                  generated_files = Dir["#{output_dir}/**/*.jpg"]
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  # Check for missing files and extra unexpected files
         | 
| 81 | 
            +
                  missing_files = expected_files.sort - generated_files.sort
         | 
| 82 | 
            +
                  extra_unexepcted_files = generated_files.sort - expected_files.sort
         | 
| 83 | 
            +
                  # TODO: Remove this `if` statement and `warn` method call once the generate_vips_dzsave_tiles implementation works properly.
         | 
| 84 | 
            +
                  if missing_files.length.positive? || extra_unexepcted_files.length.positive?
         | 
| 85 | 
            +
                    warn "generate_with_vips_dzsave test:\nmissing_files: #{missing_files.length}, extra_unexepcted_files: #{extra_unexepcted_files.length}"
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                  expect(missing_files).to eq([])
         | 
| 88 | 
            +
                  expect(extra_unexepcted_files).to eq([])
         | 
| 89 | 
            +
                ensure
         | 
| 90 | 
            +
                  FileUtils.rm_rf(output_dir) if File.exist?(output_dir)
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
            end
         | 
| 94 | 
            +
             | 
| @@ -3,21 +3,19 @@ require 'tmpdir' | |
| 3 3 |  | 
| 4 4 | 
             
            describe Imogen, vips: true do
         | 
| 5 5 | 
             
              describe ".with_image" do
         | 
| 6 | 
            -
                 | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 6 | 
            +
                let(:source_path) { fixture('sample.jpg').path }
         | 
| 7 | 
            +
                let(:revalidate) { true }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                it 'calls Vips::Image.new_from_file in the expected way when no opts are passed' do
         | 
| 10 | 
            +
                  expect(Vips::Image).to receive(:new_from_file).with(source_path)
         | 
| 11 | 
            +
                  Imogen.with_image(source_path) do |img|
         | 
| 10 12 | 
             
                    # don't need to do anything with the image for this test
         | 
| 11 13 | 
             
                  end
         | 
| 12 14 | 
             
                end
         | 
| 13 15 |  | 
| 14 | 
            -
                it ' | 
| 15 | 
            -
                  expect(Vips::Image).to receive(:new_from_file). | 
| 16 | 
            -
                   | 
| 17 | 
            -
                  Imogen.with_image(fixture('sample.jpg').path, nocache: false) do |img|
         | 
| 18 | 
            -
                    # don't need to do anything with the image for this test
         | 
| 19 | 
            -
                  end
         | 
| 20 | 
            -
                  Imogen.with_image(fixture('sample.jpg').path) do |img|
         | 
| 16 | 
            +
                it 'passes the revalidate option to the underlying Vips::Image.new_from_file method' do
         | 
| 17 | 
            +
                  expect(Vips::Image).to receive(:new_from_file).with(source_path, revalidate: revalidate)
         | 
| 18 | 
            +
                  Imogen.with_image(source_path, revalidate: revalidate) do |img|
         | 
| 21 19 | 
             
                    # don't need to do anything with the image for this test
         | 
| 22 20 | 
             
                  end
         | 
| 23 21 | 
             
                end
         | 
| @@ -0,0 +1,106 @@ | |
| 1 | 
            +
            require 'imogen/iiif'
         | 
| 2 | 
            +
            require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
         | 
| 3 | 
            +
            describe Imogen::Iiif::Tiles, type: :unit do
         | 
| 4 | 
            +
              describe 'OpenSeadragon Tile expectations' do
         | 
| 5 | 
            +
                let(:tile_size) { 512 }
         | 
| 6 | 
            +
                let(:portrait_test_image) { ImageStub.new(1920, 3125) }
         | 
| 7 | 
            +
                let(:osd_portrait_expected_tiles) do
         | 
| 8 | 
            +
                  [
         | 
| 9 | 
            +
                    '/0,0,1024,1024/512,/0/default.jpg',
         | 
| 10 | 
            +
                    '/0,0,1920,2048/480,/0/default.jpg',
         | 
| 11 | 
            +
                    '/0,0,512,512/512,/0/default.jpg',
         | 
| 12 | 
            +
                    '/0,1024,1024,1024/512,/0/default.jpg',
         | 
| 13 | 
            +
                    '/0,1024,512,512/512,/0/default.jpg',
         | 
| 14 | 
            +
                    '/0,1536,512,512/512,/0/default.jpg',
         | 
| 15 | 
            +
                    '/0,2048,1024,1024/512,/0/default.jpg',
         | 
| 16 | 
            +
                    '/0,2048,1920,1077/480,/0/default.jpg',
         | 
| 17 | 
            +
                    '/0,2048,512,512/512,/0/default.jpg',
         | 
| 18 | 
            +
                    '/0,2560,512,512/512,/0/default.jpg',
         | 
| 19 | 
            +
                    '/0,3072,1024,53/512,/0/default.jpg',
         | 
| 20 | 
            +
                    '/0,3072,512,53/512,/0/default.jpg',
         | 
| 21 | 
            +
                    '/0,512,512,512/512,/0/default.jpg',
         | 
| 22 | 
            +
                    '/1024,0,512,512/512,/0/default.jpg',
         | 
| 23 | 
            +
                    '/1024,0,896,1024/448,/0/default.jpg',
         | 
| 24 | 
            +
                    '/1024,1024,512,512/512,/0/default.jpg',
         | 
| 25 | 
            +
                    '/1024,1024,896,1024/448,/0/default.jpg',
         | 
| 26 | 
            +
                    '/1024,1536,512,512/512,/0/default.jpg',
         | 
| 27 | 
            +
                    '/1024,2048,512,512/512,/0/default.jpg',
         | 
| 28 | 
            +
                    '/1024,2048,896,1024/448,/0/default.jpg',
         | 
| 29 | 
            +
                    '/1024,2560,512,512/512,/0/default.jpg',
         | 
| 30 | 
            +
                    '/1024,3072,512,53/512,/0/default.jpg',
         | 
| 31 | 
            +
                    '/1024,3072,896,53/448,/0/default.jpg',
         | 
| 32 | 
            +
                    '/1024,512,512,512/512,/0/default.jpg',
         | 
| 33 | 
            +
                    '/1536,0,384,512/384,/0/default.jpg',
         | 
| 34 | 
            +
                    '/1536,1024,384,512/384,/0/default.jpg',
         | 
| 35 | 
            +
                    '/1536,1536,384,512/384,/0/default.jpg',
         | 
| 36 | 
            +
                    '/1536,2048,384,512/384,/0/default.jpg',
         | 
| 37 | 
            +
                    '/1536,2560,384,512/384,/0/default.jpg',
         | 
| 38 | 
            +
                    '/1536,3072,384,53/384,/0/default.jpg',
         | 
| 39 | 
            +
                    '/1536,512,384,512/384,/0/default.jpg',
         | 
| 40 | 
            +
                    '/512,0,512,512/512,/0/default.jpg',
         | 
| 41 | 
            +
                    '/512,1024,512,512/512,/0/default.jpg',
         | 
| 42 | 
            +
                    '/512,1536,512,512/512,/0/default.jpg',
         | 
| 43 | 
            +
                    '/512,2048,512,512/512,/0/default.jpg',
         | 
| 44 | 
            +
                    '/512,2560,512,512/512,/0/default.jpg',
         | 
| 45 | 
            +
                    '/512,3072,512,53/512,/0/default.jpg',
         | 
| 46 | 
            +
                    '/512,512,512,512/512,/0/default.jpg',
         | 
| 47 | 
            +
                    '/full/120,/0/default.jpg',
         | 
| 48 | 
            +
                    '/full/240,/0/default.jpg',
         | 
| 49 | 
            +
                  ]
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                let(:landscape_test_image) { ImageStub.new(1920, 1213) }
         | 
| 53 | 
            +
                let(:osd_landscape_expected_tiles) do
         | 
| 54 | 
            +
                  [
         | 
| 55 | 
            +
                    '/0,0,1024,1024/512,/0/default.jpg',
         | 
| 56 | 
            +
                    '/0,0,512,512/512,/0/default.jpg',
         | 
| 57 | 
            +
                    '/0,1024,1024,189/512,/0/default.jpg',
         | 
| 58 | 
            +
                    '/0,1024,512,189/512,/0/default.jpg',
         | 
| 59 | 
            +
                    '/0,512,512,512/512,/0/default.jpg',
         | 
| 60 | 
            +
                    '/1024,0,512,512/512,/0/default.jpg',
         | 
| 61 | 
            +
                    '/1024,0,896,1024/448,/0/default.jpg',
         | 
| 62 | 
            +
                    '/1024,1024,512,189/512,/0/default.jpg',
         | 
| 63 | 
            +
                    '/1024,1024,896,189/448,/0/default.jpg',
         | 
| 64 | 
            +
                    '/1024,512,512,512/512,/0/default.jpg',
         | 
| 65 | 
            +
                    '/1536,0,384,512/384,/0/default.jpg',
         | 
| 66 | 
            +
                    '/1536,1024,384,189/384,/0/default.jpg',
         | 
| 67 | 
            +
                    '/1536,512,384,512/384,/0/default.jpg',
         | 
| 68 | 
            +
                    '/512,0,512,512/512,/0/default.jpg',
         | 
| 69 | 
            +
                    '/512,1024,512,189/512,/0/default.jpg',
         | 
| 70 | 
            +
                    '/512,512,512,512/512,/0/default.jpg',
         | 
| 71 | 
            +
                    '/full/240,/0/default.jpg',
         | 
| 72 | 
            +
                    '/full/480,/0/default.jpg'
         | 
| 73 | 
            +
                  ]
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                describe '#for' do
         | 
| 77 | 
            +
                  it 'should produce the expected tiles for a sample dimension portrait orientation image' do
         | 
| 78 | 
            +
                    actual = []
         | 
| 79 | 
            +
                    expected = osd_portrait_expected_tiles
         | 
| 80 | 
            +
                    described_class.for(portrait_test_image, '', :jpeg, tile_size) do |img, dest_path, format, opts|
         | 
| 81 | 
            +
                      actual << dest_path
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    # Check for missing files and extra unexpected files
         | 
| 85 | 
            +
                    missing_files = expected.sort - actual.sort
         | 
| 86 | 
            +
                    extra_unexepcted_files = actual.sort - expected.sort
         | 
| 87 | 
            +
                    expect(missing_files).to eq([])
         | 
| 88 | 
            +
                    expect(extra_unexepcted_files).to eq([])
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  it 'should produce the expected tiles for a sample dimension landscape orientation image' do
         | 
| 92 | 
            +
                    actual = []
         | 
| 93 | 
            +
                    expected = osd_landscape_expected_tiles
         | 
| 94 | 
            +
                    described_class.for(landscape_test_image, '', :jpeg, tile_size) do |img, dest_path, format, opts|
         | 
| 95 | 
            +
                      actual << dest_path
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    # Check for missing files and extra unexpected files
         | 
| 99 | 
            +
                    missing_files = expected.sort - actual.sort
         | 
| 100 | 
            +
                    extra_unexepcted_files = actual.sort - expected.sort
         | 
| 101 | 
            +
                    expect(missing_files).to eq([])
         | 
| 102 | 
            +
                    expect(extra_unexepcted_files).to eq([])
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
            end
         | 
| @@ -2,77 +2,97 @@ require 'imogen/iiif' | |
| 2 2 | 
             
            require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
         | 
| 3 3 | 
             
            describe Imogen::Iiif::Tiles, type: :unit do
         | 
| 4 4 | 
             
              before(:all) do
         | 
| 5 | 
            -
                @test_image = ImageStub.new(175,131)
         | 
| 5 | 
            +
                @test_image = ImageStub.new(175, 131)
         | 
| 6 6 | 
             
              end
         | 
| 7 | 
            -
              let(: | 
| 7 | 
            +
              let(:tile512) {
         | 
| 8 | 
            +
                [
         | 
| 9 | 
            +
                  '/full/full/0/default.jpg',
         | 
| 10 | 
            +
                  '/full/175,/0/default.jpg'
         | 
| 11 | 
            +
                ]
         | 
| 12 | 
            +
              }
         | 
| 13 | 
            +
              let(:tile256) {
         | 
| 14 | 
            +
                [
         | 
| 15 | 
            +
                  '/full/full/0/default.jpg',
         | 
| 16 | 
            +
                  '/full/175,/0/default.jpg',
         | 
| 17 | 
            +
                  '/full/88,/0/default.jpg'
         | 
| 18 | 
            +
                ]
         | 
| 19 | 
            +
              }
         | 
| 8 20 | 
             
              let(:tile128) {
         | 
| 9 21 | 
             
                [
         | 
| 10 | 
            -
                   | 
| 11 | 
            -
                   | 
| 12 | 
            -
                   | 
| 13 | 
            -
                   | 
| 22 | 
            +
                  '/0,0,128,128/128,/0/default.jpg',
         | 
| 23 | 
            +
                  '/0,128,128,3/128,/0/default.jpg',
         | 
| 24 | 
            +
                  '/128,0,47,128/47,/0/default.jpg',
         | 
| 25 | 
            +
                  '/128,128,47,3/47,/0/default.jpg',
         | 
| 26 | 
            +
                  '/full/88,/0/default.jpg',
         | 
| 27 | 
            +
                  '/full/44,/0/default.jpg'
         | 
| 14 28 | 
             
                ]
         | 
| 15 29 | 
             
              }
         | 
| 16 30 | 
             
              let(:tile64) {
         | 
| 17 31 | 
             
                [
         | 
| 18 | 
            -
                   | 
| 19 | 
            -
                   | 
| 20 | 
            -
                   | 
| 21 | 
            -
                   | 
| 22 | 
            -
                   | 
| 23 | 
            -
                   | 
| 24 | 
            -
                   | 
| 25 | 
            -
                   | 
| 26 | 
            -
                   | 
| 32 | 
            +
                  '/0,0,64,64/64,/0/default.jpg',
         | 
| 33 | 
            +
                  '/0,64,64,64/64,/0/default.jpg',
         | 
| 34 | 
            +
                  '/0,128,64,3/64,/0/default.jpg',
         | 
| 35 | 
            +
                  '/64,0,64,64/64,/0/default.jpg',
         | 
| 36 | 
            +
                  '/64,64,64,64/64,/0/default.jpg',
         | 
| 37 | 
            +
                  '/64,128,64,3/64,/0/default.jpg',
         | 
| 38 | 
            +
                  '/128,0,47,64/47,/0/default.jpg',
         | 
| 39 | 
            +
                  '/128,64,47,64/47,/0/default.jpg',
         | 
| 40 | 
            +
                  '/128,128,47,3/47,/0/default.jpg',
         | 
| 41 | 
            +
                  '/0,0,128,128/64,/0/default.jpg',
         | 
| 42 | 
            +
                  '/0,128,128,3/64,/0/default.jpg',
         | 
| 43 | 
            +
                  '/128,0,47,128/24,/0/default.jpg',
         | 
| 44 | 
            +
                  '/128,128,47,3/24,/0/default.jpg',
         | 
| 45 | 
            +
                  '/full/44,/0/default.jpg',
         | 
| 46 | 
            +
                  '/full/22,/0/default.jpg'
         | 
| 27 47 | 
             
                ]
         | 
| 28 48 | 
             
              }
         | 
| 29 | 
            -
              describe '# | 
| 30 | 
            -
                it 'should calculate for  | 
| 31 | 
            -
                   | 
| 32 | 
            -
             | 
| 33 | 
            -
                     | 
| 34 | 
            -
                     | 
| 35 | 
            -
                     | 
| 49 | 
            +
              describe '#scale_factors_for' do
         | 
| 50 | 
            +
                it 'should calculate the expected scale factors for the given image width, height, and tile_size' do
         | 
| 51 | 
            +
                  {
         | 
| 52 | 
            +
                    32 =>  [1, 2, 4, 8, 16],
         | 
| 53 | 
            +
                    64 =>  [1, 2, 4, 8],
         | 
| 54 | 
            +
                    128 => [1, 2, 4],
         | 
| 55 | 
            +
                    256 => [1, 2],
         | 
| 56 | 
            +
                    512 => [1]
         | 
| 57 | 
            +
                  }.each do |tile_size, expected_scale_factors|
         | 
| 58 | 
            +
                    expect(
         | 
| 59 | 
            +
                      described_class.scale_factors_for(@test_image.width, @test_image.height, tile_size)
         | 
| 60 | 
            +
                    ).to eq(expected_scale_factors)
         | 
| 36 61 | 
             
                  end
         | 
| 37 62 | 
             
                end
         | 
| 38 63 | 
             
              end
         | 
| 39 64 | 
             
              describe '#for' do
         | 
| 40 | 
            -
                it 'should produce  | 
| 41 | 
            -
                   | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
                     | 
| 65 | 
            +
                it 'should produce the expected tiles for different tile sizes' do
         | 
| 66 | 
            +
                  {
         | 
| 67 | 
            +
                    512 => tile512,
         | 
| 68 | 
            +
                    256 => tile256,
         | 
| 69 | 
            +
                    128 => tile128,
         | 
| 70 | 
            +
                    64 => tile64
         | 
| 71 | 
            +
                  }.each do |tile_size, expected_tiles|
         | 
| 72 | 
            +
                    actual = []
         | 
| 73 | 
            +
                    described_class.for(@test_image, '', :jpeg, tile_size) do |img, dest_path, format, opts|
         | 
| 74 | 
            +
                      actual << dest_path
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
                    expect(actual).to eql(expected_tiles)
         | 
| 45 77 | 
             
                  end
         | 
| 46 | 
            -
                  expect(actual).to eql(expected)
         | 
| 47 78 | 
             
                end
         | 
| 48 | 
            -
                it 'should produce  | 
| 49 | 
            -
                  expected =  | 
| 50 | 
            -
                  expected.uniq!
         | 
| 51 | 
            -
                  actual = []
         | 
| 52 | 
            -
                  subject.for(@test_image,'',:jpeg,128) do |img,dest_path,format,opts|
         | 
| 53 | 
            -
                    actual << dest_path
         | 
| 54 | 
            -
                  end
         | 
| 55 | 
            -
                  actual.uniq!
         | 
| 56 | 
            -
                  expect(actual.sort).to eql(expected.sort)
         | 
| 57 | 
            -
                end
         | 
| 58 | 
            -
                it 'should produce a single tileset' do
         | 
| 59 | 
            -
                  expected = fullSize + tile128 + tile64
         | 
| 79 | 
            +
                it 'should produce png when requested' do
         | 
| 80 | 
            +
                  expected = tile256.collect {|x| x.sub(/jpg$/, 'png')}
         | 
| 60 81 | 
             
                  expected.uniq!
         | 
| 61 82 | 
             
                  actual = []
         | 
| 62 | 
            -
                   | 
| 83 | 
            +
                  described_class.for(@test_image, '', :png, 256) do |img, dest_path, format, opts|
         | 
| 63 84 | 
             
                    actual << dest_path
         | 
| 64 85 | 
             
                  end
         | 
| 65 | 
            -
                  actual. | 
| 66 | 
            -
                  expect(actual.sort).to eql(expected.sort)
         | 
| 86 | 
            +
                  expect(actual).to eql(expected)
         | 
| 67 87 | 
             
                end
         | 
| 68 | 
            -
                it 'should produce  | 
| 69 | 
            -
                  expected =  | 
| 88 | 
            +
                it 'should produce the expected quality when requested' do
         | 
| 89 | 
            +
                  expected = tile256.collect {|x| x.sub(/default.jpg$/, 'color.jpg')}
         | 
| 70 90 | 
             
                  expected.uniq!
         | 
| 71 91 | 
             
                  actual = []
         | 
| 72 | 
            -
                   | 
| 92 | 
            +
                  described_class.for(@test_image, '', :jpg, 256, 'color') do |img, dest_path, format, opts|
         | 
| 73 93 | 
             
                    actual << dest_path
         | 
| 74 94 | 
             
                  end
         | 
| 75 95 | 
             
                  expect(actual).to eql(expected)
         | 
| 76 96 | 
             
                end
         | 
| 77 97 | 
             
              end
         | 
| 78 | 
            -
            end
         | 
| 98 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: imogen
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.4.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Ben Armintor
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire:
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date:  | 
| 12 | 
            +
            date: 2024-09-07 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: ruby-vips
         | 
| @@ -73,6 +73,7 @@ files: | |
| 73 73 | 
             
            - spec/fixtures/files/sample.jpg
         | 
| 74 74 | 
             
            - spec/integration/imogen_autocrop_spec.rb
         | 
| 75 75 | 
             
            - spec/integration/imogen_iiif_spec.rb
         | 
| 76 | 
            +
            - spec/integration/imogen_iiif_tiles_spec.rb
         | 
| 76 77 | 
             
            - spec/integration/imogen_spec.rb
         | 
| 77 78 | 
             
            - spec/integration/transparent_image_conversion_spec.rb
         | 
| 78 79 | 
             
            - spec/spec_helper.rb
         | 
| @@ -80,6 +81,7 @@ files: | |
| 80 81 | 
             
            - spec/unit/imogen_iiif_region_spec.rb
         | 
| 81 82 | 
             
            - spec/unit/imogen_iiif_rotate_spec.rb
         | 
| 82 83 | 
             
            - spec/unit/imogen_iiif_size_spec.rb
         | 
| 84 | 
            +
            - spec/unit/imogen_iiif_tiles_openseadragon_expectations_spec.rb
         | 
| 83 85 | 
             
            - spec/unit/imogen_iiif_tiles_spec.rb
         | 
| 84 86 | 
             
            homepage: https://github.com/cul/imogen
         | 
| 85 87 | 
             
            licenses: []
         | 
| @@ -99,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 99 101 | 
             
                - !ruby/object:Gem::Version
         | 
| 100 102 | 
             
                  version: '0'
         | 
| 101 103 | 
             
            requirements: []
         | 
| 102 | 
            -
            rubygems_version: 3. | 
| 104 | 
            +
            rubygems_version: 3.4.10
         | 
| 103 105 | 
             
            signing_key:
         | 
| 104 106 | 
             
            specification_version: 4
         | 
| 105 107 | 
             
            summary: IIIF image derivative generation helpers for Vips
         |