Skip to content

Commit

Permalink
Two pass conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoyne committed Sep 7, 2017
1 parent e85d165 commit 6303adf
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 50 deletions.
35 changes: 35 additions & 0 deletions app/models/riiif/transformation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module Riiif
# Represents a IIIF request
class Transformation
attr_reader :crop, :size, :quality, :rotation, :format
def initialize(crop, size, quality, rotation, format)
@crop = crop
@size = size
@quality = quality
@rotation = rotation
@format = format
end

# Create a clone of this Transformation, scaled by the factor
# @param [Integer] factor the scale for the new transformation
# @return [Transformation] a new transformation, scaled by factor
def reduce(factor)
Transformation.new(crop.dup,
size.reduce(factor),
quality,
rotation,
format)
end

# Create a clone of this Transformation, without the crop
# @return [Transformation] a new transformation
# TODO: it would be nice if we didn't need image_info
def without_crop(image_info)
Transformation.new(Region::Full.new(image_info),
size.dup,
quality,
rotation,
format)
end
end
end
3 changes: 2 additions & 1 deletion app/services/riiif/command_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ class CommandRunner
include ActiveSupport::Benchmarkable
delegate :logger, to: :Rails

# @return [Sting] all the image data
# TODO: this is being loaded into memory. We could make this a stream.
# @return [String] all the image data
def execute(command)
out = nil
benchmark("Riiif executed #{command}") do
Expand Down
17 changes: 6 additions & 11 deletions app/services/riiif/kakadu_command_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ def initialize(path, info, transformation)

attr_reader :path, :info, :transformation

# @param tmp_file [String] the path to the temporary file
# @return [String] a command for running kdu_expand to produce the requested output
def command
# TODO: we must delete this link
::File.symlink('/dev/stdout', link_path)
[external_command, quiet, input, threads, region, reduce, output(link_path)].join
def command(tmp_file)
[external_command, quiet, input, threads, region, reduce, output(tmp_file)].join
end

def reduction_factor
Expand All @@ -34,16 +33,12 @@ def reduction_factor

private

def link_path
@link_path ||= LinkNameService.create
end

def input
" -i #{path}"
end

def output(link_path)
" -o #{link_path}"
def output(output_filename)
" -o #{output_filename}"
end

def threads
Expand All @@ -56,7 +51,7 @@ def quiet

def region
region_arg = transformation.crop.to_kakadu
" -region #{region_arg}" if region_arg
" -region \"#{region_arg}\"" if region_arg
end

# kdu_expand is not capable of arbitrary scaling, but it does
Expand Down
16 changes: 12 additions & 4 deletions app/services/riiif/size/percent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ module Size
# of the extracted region. The aspect ratio of the returned image is the same as that
# of the extracted region.
class Percent < Resize
def initialize(info, n)
def initialize(info, percentage)
@image_info = info
@n = n
@percentage = percentage
end

attr_reader :percentage

# @return [String] a resize directive for imagemagick to use
def to_imagemagick
"#{@n}%"
"#{percentage}%"
end

# @return [Integer] the height in pixels
Expand All @@ -29,12 +31,18 @@ def reduce?
true
end

# @param [Integer] factor number of times to reduce by 1/2
def reduce(factor)
pct = percentage.to_f * 2**factor
Percent.new(image_info, pct)
end

private

# @param [Integer] value a value to convert to the percentage
# @return [Float]
def percent_of(value)
value * Integer(@n).to_f / 100
value * Integer(percentage).to_f / 100
end
end
end
Expand Down
20 changes: 9 additions & 11 deletions app/transformers/riiif/abstract_transformer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,26 @@ module Riiif
# Transforms an image using a backend
class AbstractTransformer
# @param path [String] The path of the source image file
# @param info [ImageInformation] information about the source
# @param image_info [ImageInformation] information about the source
# @param [Transformation] transformation
def self.transform(path, info, transformation)
new(path, info, transformation).transform
def self.transform(path, image_info, transformation)
new(path, image_info, transformation).transform
end

def initialize(path, info, transformation)
def initialize(path, image_info, transformation)
@path = path
@info = info
@image_info = image_info
@transformation = transformation
end

attr_reader :path, :info, :transformation
attr_reader :path, :image_info, :transformation

def transform
builder = command_factory.new(path, info, transformation)
post_process(execute(builder.command), builder.reduction_factor)
execute(command_builder.command)
end

# override this method in subclasses if we need to transform the output data
def post_process(image, _reduction_factor)
image
def command_builder
@command_builder ||= command_factory.new(path, image_info, transformation)
end

delegate :execute, to: Riiif::CommandRunner
Expand Down
41 changes: 27 additions & 14 deletions app/transformers/riiif/kakadu_transformer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,35 @@ def command_factory
KakaduCommandFactory
end

def transform
with_tempfile do |file_name|
execute(command_builder.command(file_name))
post_process(file_name, command_builder.reduction_factor)
end
end

def with_tempfile
Tempfile.open(['riiif-intermediate', '.bmp']) do |f|
yield f.path
end
end

# The data we get back from kdu_expand is a bmp and we need to change it
# to the requested format by calling Imagemagick.
# TODO: Calculate a new set of transforms with respect to reduction_factor
def post_process(data, reduction_factor)
puts "Reduction factor is #{reduction_factor}"
data_io = StringIO.new(data)
data_io.binmode
out = ''
command = "/usr/local/bin/convert - #{transformation.format}:-"
IO.popen(command, 'r+b') do |io|
io.write(data_io.read(4096)) until data_io.eof?
io.close_write
# Read from convert into our buffer
out << io.read(4096) until io.eof?
end
out
def post_process(intermediate_file, reduction_factor)
# Calculate a new set of transforms with respect to reduction_factor
transformation = if reduction_factor
self.transformation.without_crop(image_info).reduce(reduction_factor)
else
self.transformation.without_crop(image_info)
end
Riiif::File.new(intermediate_file).extract(transformation, image_info)
end

private

def tmp_path
@link_path ||= LinkNameService.create
end
end
end
1 change: 0 additions & 1 deletion lib/riiif.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class ImageNotFoundError < Error; end
# This error is raised when Riiif can't convert an image
class ConversionError < Error; end

Transformation = Struct.new(:crop, :size, :quality, :rotation, :format)
mattr_accessor :not_found_image # the image to use when a lookup fails
mattr_accessor :unauthorized_image # the image to use when a user doesn't have access

Expand Down
42 changes: 42 additions & 0 deletions spec/models/riiif/transformation_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Riiif::Transformation do
subject(:transformation) do
Riiif::Transformation.new(region,
size,
quality,
rotation,
fmt)
end

let(:region) { Riiif::Region::Full.new(image_info) }
let(:size) { Riiif::Size::Percent.new(image_info, 20) }
let(:quality) { nil }
let(:rotation) { nil }
let(:fmt) { nil }
let(:image_info) { double('Image info', height: 4381, width: 6501) }

describe 'reduce' do
subject { transformation.reduce(factor) }
context 'when reduced by 2' do
let(:factor) { 2 }
let(:size) { Riiif::Size::Percent.new(image_info, 20) }

it 'downsamples the size' do
expect(subject.size).to be_kind_of Riiif::Size::Percent
expect(subject.size.percentage).to eq 80.0
end
end
end

describe 'without_crop' do
let(:region) { Riiif::Region::Absolute.new(image_info, 5, 6, 7, 8) }

subject { transformation.without_crop(image_info) }
it 'nullifies the crop' do
expect(subject.crop).to be_kind_of Riiif::Region::Full
end
end
end
12 changes: 4 additions & 8 deletions spec/services/riiif/kakadu_command_factory_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@
end

describe '#command' do
subject { instance.command }
before do
allow(::File).to receive(:symlink)
allow(Riiif::LinkNameService).to receive(:create).and_return('/tmp/bar.bmp')
end
subject { instance.command '/tmp/bar.bmp' }

context 'with a full size image' do
it { is_expected.to eq 'kdu_expand -quiet -i foo.jp2 -num_threads 4 -o /tmp/bar.bmp' }
Expand All @@ -43,17 +39,17 @@

context 'with absolute' do
let(:region) { Riiif::Region::Absolute.new(info, 25, 75, 150, 100) }
it { is_expected.to eq ' -region {0.25,0.08333333333333333},{0.3333333333333333,0.5}' }
it { is_expected.to eq ' -region "{0.25,0.08333333333333333},{0.3333333333333333,0.5}"' }
end

context 'with a square' do
let(:region) { Riiif::Region::Square.new(info) }
it { is_expected.to eq ' -region {0.0,0},{1.0,1.0}' }
it { is_expected.to eq ' -region "{0.0,0},{1.0,1.0}"' }
end

context 'with a percentage' do
let(:region) { Riiif::Region::Percentage.new(info, 20, 30, 40, 50) }
it { is_expected.to eq ' -region {0.3,0.2},{0.5,0.4}' }
it { is_expected.to eq ' -region "{0.3,0.2},{0.5,0.4}"' }
end
end

Expand Down
69 changes: 69 additions & 0 deletions spec/transformers/riiif/kakadu_transformer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Riiif::KakaduTransformer do
subject(:instance) { described_class.new(path, image_info, transformation) }

let(:image_info) { double('Image info', height: 4381, width: 6501) }
let(:path) { 'baseball.jp2' }
let(:region) { Riiif::Region::Full.new(image_info) }
let(:size) { Riiif::Size::Full.new }
let(:quality) { nil }
let(:rotation) { nil }
let(:fmt) { 'jpg' }

let(:transformation) do
Riiif::Transformation.new(region,
size,
quality,
rotation,
fmt)
end

describe '#transform' do
let(:image_data) { double }

subject(:transform) { instance.transform }

before do
allow(instance).to receive(:with_tempfile).and_yield('/tmp/foo.bmp')
end

context 'when reduction_factor is 0' do
let(:reduction_factor) { 0 }
it 'calls the Imagemagick transform' do
expect(Riiif::CommandRunner).to receive(:execute)
.with('kdu_expand -quiet -i baseball.jp2 -num_threads 4 -o /tmp/foo.bmp')
expect(Riiif::CommandRunner).to receive(:execute)
.with('convert -quality 85 -sampling-factor 4:2:0 -strip /tmp/foo.bmp jpg:-')
transform
end
end

context 'when reduction_factor is 1' do
let(:size) { Riiif::Size::Percent.new(image_info, 30) }
let(:reduction_factor) { 1 }

it 'calls the Imagemagick transform' do
expect(Riiif::CommandRunner).to receive(:execute)
.with('kdu_expand -quiet -i baseball.jp2 -num_threads 4 -reduce 1 -o /tmp/foo.bmp')
expect(Riiif::CommandRunner).to receive(:execute)
.with('convert -resize 60.0% -quality 85 -sampling-factor 4:2:0 -strip /tmp/foo.bmp jpg:-')
transform
end
end

context 'when reduction_factor is 2' do
let(:size) { Riiif::Size::Percent.new(image_info, 20) }
let(:reduction_factor) { 2 }
it 'calls the Imagemagick transform' do
expect(Riiif::CommandRunner).to receive(:execute)
.with('kdu_expand -quiet -i baseball.jp2 -num_threads 4 -reduce 2 -o /tmp/foo.bmp')
expect(Riiif::CommandRunner).to receive(:execute)
.with('convert -resize 80.0% -quality 85 -sampling-factor 4:2:0 -strip /tmp/foo.bmp jpg:-')
transform
end
end
end
end

0 comments on commit 6303adf

Please sign in to comment.