#!/usr/bin/env ruby
require 'mini_magick'
require 'optionparser'
Dimension = Struct.new(:width, :height)
Rect = Struct.new(:x, :y, :width, :height)
Rects = Struct.new(:left, :right)
class Screen
attr_reader :pixel, :physical, :scale
def initialize(diag, width, height)
@pixel = Dimension.new(width, height)
aspectRatioDiagonal = Math.sqrt(width ** 2 + height ** 2)
factor = Rational(diag, aspectRatioDiagonal)
@physical = Dimension.new(width * factor, height * factor)
@scale = Rational(@pixel.height, @physical.height)
end
end
class Image
def initialize(path, width, height)
@path = path
@width = width
@height = height
end
def split(left, right, nudge, start_left, gap, left_out, right_out)
crop = rects(left, right, nudge, start_left, gap)
puts "Cropping left to #{crop.left.width} x #{crop.left.height} + #{crop.left.x} x #{crop.left.y}"
image = MiniMagick::Image.open(@path)
image.crop "#{crop.left.width}x#{crop.left.height}+#{crop.left.x}+#{crop.left.y}"
image.resize "#{left.pixel.width}x#{left.pixel.height}\!"
image.write left_out
puts "Cropping right to #{crop.right.width} x #{crop.right.height} + #{crop.right.x} x #{crop.right.y}"
image = MiniMagick::Image.open(@path)
image.crop "#{crop.right.width}x#{crop.right.height}+#{crop.right.x}+#{crop.right.y}"
image.resize "#{right.pixel.width}x#{right.pixel.height}\!"
image.write right_out
end
def explain(left, right, nudge, start_left, gap, out)
crop = rects(left, right, nudge, start_left, gap)
puts "Would crop left to #{crop.left.width} x #{crop.left.height} + #{crop.left.x} x #{crop.left.y}"
puts "Would crop right to #{crop.right.width} x #{crop.right.height} + #{crop.right.x} x #{crop.right.y}"
image = MiniMagick::Image.open(@path)
image.combine_options do |c|
c.fill "none"
c.stroke "red"
c.strokewidth "5"
c.draw "rectangle #{crop.left.x},#{crop.left.y} #{crop.left.x+crop.left.width},#{crop.left.y+crop.left.height}"
end
image.combine_options do |c|
c.fill "none"
c.stroke "green"
c.strokewidth "5"
c.draw "rectangle #{crop.right.x},#{crop.right.y} #{crop.right.x+crop.right.width},#{crop.right.y+crop.right.height}"
end
image.write out
end
def rects(left, right, nudge, start_left, gap)
total_width = left.physical.width + right.physical.width
left_mag = Rational(left.scale, right.scale)
px_per_unit = Rational(@width - start_left - gap, total_width)
left_crop_height = (left.physical.height * px_per_unit * left_mag).to_i
right_crop_height = (right.physical.height * px_per_unit).to_i
if left_crop_height > left.pixel.height
px_per_unit = Rational(@height, left.physical.height)
end
if right_crop_height > right.pixel.height
px_per_unit = Rational(@height, right.physical.height)
end
left_crop_width = (left.physical.width * px_per_unit * left_mag).to_i
left_crop_height = (left.physical.height * px_per_unit * left_mag).to_i
left_nudge = (left.physical.height < right.physical.height ? nudge * px_per_unit : 0).to_i
left_start = (start_left * px_per_unit).to_i
right_crop_width = (right.physical.width * px_per_unit).to_i
right_crop_height = (right.physical.height * px_per_unit).to_i
right_nudge = (right.physical.height < left.physical.height ? nudge * px_per_unit : 0).to_i
right_start = left_crop_width + left_start + (gap * px_per_unit).to_i
Rects.new(
Rect.new(left_start, left_nudge, left_crop_width, left_crop_height),
Rect.new(right_start, right_nudge, right_crop_width, right_crop_height)
)
end
end
args = {
left: {},
right: {},
nudge: 0,
start_left: 0,
gap: 0,
}
opt_parser = OptionParser.new do |opts|
opts.banner = "Usage: split-image [options]"
opts.on("--in=PATH", "Path to image to split") do |n|
args[:in] = n
end
opts.on("--nudge=HEIGHT", "Distance to nudge smallest image down") do |n|
args[:nudge] = n.to_f
end
opts.on("--start-left=WIDTH", "Distance to start cropping from the left") do |n|
args[:start_left] = n.to_f
end
opts.on("--gap=WIDTH", "Distance between left and right screens") do |n|
args[:gap] = n.to_f
end
opts.on("--explain=OUT", "Draw rectangles explaining what will be cut") do |n|
args[:explain] = n
end
[:left, :right].each do |screen|
opts.on("--out-#{screen}=PATH", "Path to write #{screen} image") do |n|
args[screen][:out] = n
end
opts.on("--#{screen}-width=PIXELS", "Width of #{screen} screen") do |n|
args[screen][:width] = n.to_i
end
opts.on("--#{screen}-height=PIXELS", "Height of #{screen} screen") do |n|
args[screen][:height] = n.to_i
end
opts.on("--#{screen}-diagonal=DIAGONAL", "Diagonal size of #{screen} screen") do |n|
args[screen][:diagonal] = n.to_i
end
end
end
opt_parser.parse!
left = Screen.new(args[:left][:diagonal], args[:left][:width], args[:left][:height])
right = Screen.new(args[:right][:diagonal], args[:right][:width], args[:right][:height])
image = MiniMagick::Image.open(args[:in])
image = Image.new(args[:in], image[:width], image[:height])
if args[:explain]
image.explain(left, right, args[:nudge], args[:start_left], args[:gap], args[:explain])
else
image.split(left, right, args[:nudge], args[:start_left], args[:gap], args[:left][:out], args[:right][:out])
end