Skip to content

Instantly share code, notes, and snippets.

@a2800276
Created November 16, 2011 10:14
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 a2800276/52f5154899778860cef8 to your computer and use it in GitHub Desktop.
Save a2800276/52f5154899778860cef8 to your computer and use it in GitHub Desktop.
Content-aware image cropping with ChunkyPNG

Simplest possible solution to image cropping problem:

  • grab the most "exciting" part of the image
    • "exciting" => least average according to unweighed color
  • checks every single 100 X 100 splotch, so it's very inefficient. Not checking every single subimage would be the most obvious improvement, but frankly, doing image processing in native ruby is probably not the best idea anyhow.
  • special bonus! also finds the least interesting 100X100 subimage.
  • usage: ruby chunky.rb <inputfn.png>
  • most "exciting" crop is saved as <inputfn.png>.max.png
  • most "boring" crop is saved as <inputfn.png>.min.png

This works:

  • extremely well for the duck
  • well for the cat
  • so-so for the dog

Actually in the case of the dog, the bit identified as "most boring" is a pretty good crop. Reason being that the dog picture has little variation: half big black dog, half green. All said, though, the dog crop is acceptable and by far not the worst possible choice.

Possible improvements:

  • tons of performance optimizations
  • calculating the averages differently, especially perception weighting the individual color channels or hsv components.
  • doing this properly would require more sophisticated algorithms, I doubt it would be feasible with ChunkyPNG
require 'chunky_png'
include ChunkyPNG
def usage
STDERR.puts "usage: [bla] filename.png"
exit 1
end
usage if 1 != ARGV.length
# determine "average" color of a splotch.
def avg img
sum = img.pixels.inject(0) {|sum, pixel|
sum += pixel
}
sum/img.pixels.length
end
# iterate over every possible 100 X 100
# subimage
def each img
# replace `upto` with `step` here to
# speed things up
0.upto(img.width-100) {|x|
0.upto(img.height-100) {|y|
yield img.crop(x,y,100,100)
}
}
end
img = Image.from_file(ARGV[0])
img_avg = avg(img)
max = 0
min = 0xffffffffffff
img_max, img_min = nil, nil
each(img) { |i|
d = (avg(i)-img_avg).abs
max, img_max = d, i if d > max
min, img_min = d, i if d < min
}
img_max.to_image.save(ARGV[0]+".max.png")
img_min.to_image.save(ARGV[0]+".min.png")
@rogerbraun
Copy link

Nice results, simple code. I like this solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment