Skip to content

Instantly share code, notes, and snippets.

@kaja47
Created October 11, 2012 22:51
Show Gist options
  • Save kaja47/3876064 to your computer and use it in GitHub Desktop.
Save kaja47/3876064 to your computer and use it in GitHub Desktop.
Content-aware image cropping with Scala
import javax.imageio.ImageIO
import java.io.File
import java.awt.image.BufferedImage
import java.awt.{ RenderingHints, AlphaComposite }
// Content-aware image cropping with ChunkyPNG
// https://gist.github.com/a54cd41137b678935c91
final class Cropper(file: String) {
val image = ImageIO.read(new File(file))
def cropAndScale(newWidth: Int = 100, newHeight: Int = 100) = {
val size = math.min(image.getHeight, image.getWidth)
resize(crop(size, size), newWidth, newHeight)
}
def crop(cropWidth: Int = 100, cropHeight: Int = 100) = {
var (x, y, width, height) = (0, 0, image.getWidth, image.getHeight)
val sliceLength = 16
while ((width - x) > cropWidth) {
val sliceWidth = math.min(width - x - cropWidth, sliceLength)
val left = image.getSubimage(x, 0, sliceWidth, image.getHeight)
val right = image.getSubimage(width - sliceWidth, 0, sliceWidth, image.getHeight)
if (entropy(left) < entropy(right))
x += sliceWidth
else
width -= sliceWidth
}
while ((height - y) > cropHeight) {
val sliceHeight = math.min(height - y - cropHeight, sliceLength)
val top = image.getSubimage(0, y, image.getWidth, sliceHeight)
val bottom = image.getSubimage(0, height - sliceHeight, image.getWidth, sliceHeight)
if (entropy(top) < entropy(bottom))
y += sliceHeight
else
height -= sliceHeight
}
image.getSubimage(x, y, cropWidth, cropHeight)
}
private def histogram(image: BufferedImage) = {
val hist = new Array[Int](256)
for (d <- grayscaleData(image)) hist(d) = hist(d) + 1
hist
}
/** http://www.mathworks.com/help/toolbox/images/ref/entropy.html */
private def entropy(image: BufferedImage) = {
val hist = histogram(grayscale(image))
val area = (image.getWidth * image.getHeight).toDouble
-hist.view.filter(_ > 0).foldLeft(0.0) { (e, freq) =>
val p = freq / area
e + p * math.log(p) // log2
}
}
private def resize(img: BufferedImage, w: Int, h: Int): BufferedImage = {
val resized = new BufferedImage(w, h, img.getType)
val g = resized.createGraphics()
//g.setComposite(AlphaComposite.Src)
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.drawImage(img, 0, 0, w, h, null)
g.dispose()
resized
}
private def grayscale(img: BufferedImage) = {
val gray = new BufferedImage(img.getWidth, img.getHeight, BufferedImage.TYPE_BYTE_GRAY)
val g = gray.getGraphics()
g.drawImage(img, 0, 0, null)
g.dispose()
gray
}
private def grayscaleData(img: BufferedImage) =
img.getData.getSamples(0, 0, img.getWidth, img.getHeight, 0, new Array[Int](img.getWidth * img.getHeight))
}
val r = new Cropper("duck.png").cropAndScale(100, 100)
ImageIO.write(r, "jpg", new File("duck-cropped.jpg"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment