Skip to content

Instantly share code, notes, and snippets.

@geoffyoungs
Created July 24, 2015 09:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save geoffyoungs/83615efeeb529415bf78 to your computer and use it in GitHub Desktop.
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
#!/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