Skip to content

Instantly share code, notes, and snippets.

@EmmanuelOga
Created April 24, 2012 03:26
Show Gist options
  • Save EmmanuelOga/2476153 to your computer and use it in GitHub Desktop.
Save EmmanuelOga/2476153 to your computer and use it in GitHub Desktop.
RMagick autocropper
require 'RMagick'
# Auto crop an image by detecting solid edges around them.
class AutoCropper
# How small we accept a cropped picture to be. E.G. if it was 100x100 and
# ratio 0.1, min output should be 10x10
MIN_CROP_RATIO = 0.1
attr_reader :img, :x0, :y0, :x1, :y1, :min_width, :min_height, :rows, :columns, :is_border
def initialize(img, is_border_proc = nil, min_ratio = MIN_CROP_RATIO)
@img, @min_ratio = img, min_ratio
# Coordinates
@x0, @y0 = 0, 0; @x1, @y1 = img.columns, img.rows
@min_width, @min_height = img.columns * @min_ratio, img.rows * @min_ratio
# We need a border finder proc. Provide one if none was given.
@is_border = is_border_proc || self.class.default_border_finder(img)
find_edges
end
def width
x1 - x0
end
def height
y1 - y0
end
def output
img.crop x0, y0, width, height, true
end
# Returns a Proc that, given a set of pixels (an edge of the image) decides
# whether that edge is a border or not.
def self.default_border_finder(img, samples = 5, threshold = 0.95, fuzz = 0.05)
fuzz = (2**16 * fuzz).to_i
# Returns true if the edge is a border.
lambda do |edge|
border, non_border = 0.0, 0.0
pixels = (0...samples).map { |n| edge[n * edge.length / samples] }
pixels.combination(2).each { |a, b| a.fcmp(b, fuzz) ? border += 1 : non_border += 1 }
border.to_f / (border + non_border) > threshold
end
end
private
def find_edges
return unless is_border
u = x1 - 1
x0.upto(u) { |x| width_croppable? && is_border[vline x] ? @x0 = x + 1 : break }
(u).downto(x0) { |x| width_croppable? && is_border[vline x] ? @x1 = x - 1 : break }
u = y1 - 1
0.upto(u) { |y| height_croppable? && is_border[hline y] ? @y0 = y + 1 : break }
(u).downto(y0) { |y| height_croppable? && is_border[hline y] ? @y1 = y - 1 : break }
end
def vline(x)
img.get_pixels x, y0, 1, height
end
def hline(y)
img.get_pixels x0, y, width, 1
end
def width_croppable?
width > min_width
end
def height_croppable?
height > min_height
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment