Created
July 24, 2015 09:27
-
-
Save geoffyoungs/83615efeeb529415bf78 to your computer and use it in GitHub Desktop.
Find the maximum active box from a selection of images and crop them all to a square that attempts to centre the active area
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'gdk_pixbuf2' | |
require 'fileutils' | |
# arg : String : filename | |
# sensitivity : Integer : greyscale distance between pixels for this to be considered an edge | |
# scale_to : Integer : max dimension (images are scaled down for performance reasons) | |
# | |
# Scales image down and then runs a simple edge detection algorithm in order to detect the active area | |
# | |
# returns : Array[min_x, min_y, max_x, max_y] - co-ordinates of active area | |
# | |
def get_box_for_img(arg, sensitivity, scale_to) | |
_, width, _ = Gdk::Pixbuf.get_file_info(arg) | |
pb = Gdk::Pixbuf.new(arg, scale_to, scale_to) | |
pixels = pb.pixels | |
min_x = nil | |
max_x = nil | |
min_y = nil | |
max_y = nil | |
lastrow = nil | |
pb.height.times do |row| | |
rowdata = pixels.byteslice((row * pb.rowstride), (row + 1) * pb.rowstride) | |
bytes = rowdata.unpack('C*') | |
pixel = lambda { |bytes, offset| | |
r = bytes[offset] | |
g = bytes[offset+1] | |
b = bytes[offset+2] | |
a = bytes[offset+3] if pb.n_channels == 4 | |
m = ((r * 0.3086) + (g * 0.6094) + (b * 0.0820)).to_i | |
} | |
pb.width.times do |col| | |
offset = pb.n_channels * col | |
if col < 1 | |
next | |
end | |
m = 255 | |
lp = pixel[bytes, offset] | |
cp = pixel[bytes, offset-pb.n_channels] | |
m = 0 if row >= 1 && (pixel[lastrow, offset]-lp).abs > sensitivity | |
m = 0 if (lp - cp).abs > sensitivity | |
if m.zero? | |
min_x ||= col | |
max_x ||= col | |
min_y ||= row | |
max_y ||= row | |
min_x = col if col < min_x | |
max_x = col if col > max_x | |
min_y = row if row < min_y | |
max_y = row if row > max_y | |
end | |
end | |
lastrow = bytes | |
end | |
scale = width.to_f / pb.width.to_f | |
[scale * min_x, | |
scale * min_y, | |
scale * max_x, | |
scale * max_y].map(&:to_i) | |
end | |
# pb : Gdk::Pixbfu : pixbuf object | |
# crop: Array [ min_x, min_y, max_x, max_y ] : bounds area | |
# max_size : Integer : max dimension (images are scaled down for performance reasons) | |
# | |
# Crops image to a square centered on the active area and returns a scaled down pixbuf | |
# | |
# returns : Gdk::Pixbuf : Scaled down pixbuf | |
# | |
def crop_and_scale(pb, crop, max_size) | |
x1, y1, x2, y2 = *crop | |
x, y, w, h = x1, y1, x2 - x1, y2 - y1 | |
#p [:was, x, y, w, h] | |
if w < h | |
d = h - w | |
x -= (d>>1) | |
w += d# -(d>>1) | |
p [:w, d, x, w] | |
if x < 0 | |
w -= x | |
x = 0 | |
end | |
if (x+w) > pb.width | |
x -= (x+w) - pb.width | |
w = (pb.width - x) | |
end | |
elsif h < w | |
d = w - h | |
y -= (d>>1) | |
h += d# -(d>>1) | |
#p [:w, d, y, h] | |
if y < 0 | |
h -= y | |
y = 0 | |
end | |
if (y+h) > pb.height | |
y -= (y+h) - pb.height | |
h = pb.height - y | |
end | |
end | |
#p [:is_, x, y, w, h] | |
pb = Gdk::Pixbuf.new(pb, x, y, w, h) | |
w = pb.width | |
if pb.width > max_size | |
w = max_size | |
h = (max_size * pb.height.to_f / pb.width).to_i | |
end | |
if h > max_size | |
h = max_size | |
w = (max_size * pb.width.to_f / pb.height).to_i | |
end | |
pb.scale(w, h) | |
end | |
# files : Array[ filename, ... ] : list of files | |
# pad : Integer : padding to add | |
# | |
# Scans through a list of files and gets the maximum active area | |
# | |
# returns : Array[min_x, min_y, max_x, max_y] - co-ordinates of active area | |
def get_collective_bounds(files, pad = 5) | |
bounds = nil | |
files.each do |arg| | |
r = get_box_for_img(arg, 12, 150) | |
bounds ||= r | |
bounds[0] = r[0] if r[0] < bounds[0] | |
bounds[1] = r[1] if r[1] < bounds[1] | |
bounds[2] = r[2] if r[2] > bounds[2] | |
bounds[3] = r[3] if r[3] > bounds[3] | |
end | |
_, w, h = Gdk::Pixbuf.get_file_info(files[0]) | |
# add padding | |
bounds[0] -= pad | |
bounds[1] -= pad | |
bounds[2] += pad | |
bounds[3] += pad | |
bounds[0] = 0 if bounds[0] < 0 | |
bounds[1] = 0 if bounds[1] < 0 | |
bounds[2] = w if bounds[2] > w | |
bounds[3] = h if bounds[3] > h | |
bounds | |
end | |
if __FILE__.eql? $0 | |
files = ARGV | |
if files[0] =~ /1080/ | |
outdir = files[0].sub(/1080/, "256") | |
files = Dir.glob(files[0]+"/*.jpg") | |
else | |
outdir = File.dirname(files[0]) + "/out" | |
end | |
bounds = get_collective_bounds( files, 5 ) | |
files.each do |arg| | |
pb = Gdk::Pixbuf.new(arg) | |
pb = crop_and_scale(pb, bounds, 256) | |
FileUtils.mkdir_p(outdir) | |
pb.save("#{outdir}/#{ File.basename(arg) }", arg =~ /png$/ ? "png" : "jpeg") | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment