普段ターミナル中心に bash と vim で仕事をしている身としては ちょっとした画像処理は いちいち Photoshop や GIMP を起動するより groovy スクリプトでちゃっちゃと済ませてしまいたいところです。
今回はそれら普段使いのちょっとした画像処理用コードをいろいろ書いてみます。 ここではこのとてもおいしそうな ポンシェのホワイトドーナツ 画像を例に いろいろ料理してみましょう。
このページにあるスクリプトの作動確認環境は以下の通りです。
groovy -version
Groovy Version: 2.4.6 JVM: 1.8.0_121 Vendor: Oracle Corporation OS: Mac OS X
100x100 のサイズの画像を生成してみます。
import java.awt.Image
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
System.setProperty("java.awt.headless", "true")
def toBufferedImage = { Image image->
def bufferedImage = new BufferedImage(image.width, image.height, BufferedImage.TYPE_4BYTE_ABGR)
def g = bufferedImage.graphics
g.drawImage(image, 0, 0, null)
g.dispose()
return bufferedImage
}
def doResize = { inputStream, outputStream, width, height->
def inputBufferedImage = ImageIO.read(inputStream)
def resizedImage = inputBufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH)
ImageIO.write(toBufferedImage(resizedImage), 'PNG', outputStream)
}
def inputPngFile = new File(args[0])
def outputPngFile = new File(args[1])
def resizeSpec = args[2]
int resizeWidth = resizeSpec.split(/x/)[0] as int
int resizeHeight = resizeSpec.split(/x/)[1] as int
def input = new FileInputStream( inputPngFile )
def output = new FileOutputStream( outputPngFile )
doResize(input, output, resizeWidth, resizeHeight)
input.close()
output.close()
groovy image-resize white-donuts.png white-donuts-100x100.png 100x100
inputBufferedImage.getScaledInstance の部分でリサイズ後の画像サイズを指定していますが、 このとき 縦または横の画像サイズに -1 を指定することで、縦横比を維持してリサイズすることができます。
image.getScaledInstance した結果返るImageインスタンスはそのままでは ImageIO.write できないので、 BufferedImage インスタンスに変換しています。
import java.awt.Image
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
System.setProperty("java.awt.headless", "true")
def createGrayscaleImageAndPaste = { Image pasteImage->
def grayscaleImage = new BufferedImage(pasteImage.width, pasteImage.height, BufferedImage.TYPE_BYTE_GRAY)
def g = grayscaleImage.graphics
g.drawImage(pasteImage, 0, 0, null)
g.dispose()
return grayscaleImage
}
def doGrayscale = { inputStream, outputStream->
def inputBufferedImage = ImageIO.read(inputStream)
ImageIO.write(createGrayscaleImageAndPaste(inputBufferedImage), 'PNG', outputStream)
}
def inputPngFile = new File(args[0])
def outputPngFile = new File(args[1])
def input = new FileInputStream( inputPngFile )
def output = new FileOutputStream( outputPngFile )
doGrayscale(input, output)
input.close()
output.close()
groovy image-grayscale white-donuts.png white-donuts-gray.png
BufferedImage.TYPE_BYTE_GRAY をタイプに指定して BufferedImage インスタンスを生成しておいてから、 そこに元の画像を描写する、という手順になります。
import java.awt.Image
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
System.setProperty("java.awt.headless", "true")
def toBufferedImage = { Image pasteImage->
def bufferedImage = new BufferedImage(pasteImage.width, pasteImage.height, BufferedImage.TYPE_3BYTE_BGR)
def g = bufferedImage.graphics
g.drawImage(pasteImage, 0, 0, null)
g.dispose()
return bufferedImage
}
def doConvert = { inputStream, outputStream->
def inputBufferedImage = ImageIO.read(inputStream)
ImageIO.write(toBufferedImage(inputBufferedImage), 'JPEG', outputStream)
}
def inputPngFile = new File(args[0])
def outputPngFile = new File(args[1])
def input = new FileInputStream( inputPngFile )
def output = new FileOutputStream( outputPngFile )
doConvert(input, output)
input.close()
output.close()
groovy image-to-jpeg white-donuts.png white-donuts-gray.jpg
BufferedImage.TYPE_3BYTE_BGR で BufferedImage インスタンスを生成しておいて、そこに読み込んだ元画像を配置して JPEG 形式で書き出しです。 簡単ですね。
縦横10%分回りを切り落とす形でクロップした画像を生成してみます。
import java.awt.Toolkit
import java.awt.Image
import java.awt.image.BufferedImage
import java.awt.image.CropImageFilter
import java.awt.image.FilteredImageSource
import javax.imageio.ImageIO
System.setProperty("java.awt.headless", "true")
def createWidthAndHeight = { inputStream->
def inputBufferedImage = ImageIO.read(inputStream)
return [
width: inputBufferedImage.width,
height: inputBufferedImage.height]
}
def toBufferedImageWithCrop = { Image image, Map cropSpec->
def filter = new CropImageFilter( cropSpec.left, cropSpec.top, cropSpec.right, cropSpec.bottom )
def filteredImageSource = new FilteredImageSource( image.getSource(), filter )
def croppedImage = Toolkit.getDefaultToolkit().createImage(filteredImageSource)
def bufferedImage = new BufferedImage(
(cropSpec.right - cropSpec.left),
(cropSpec.bottom - cropSpec.top),
BufferedImage.TYPE_4BYTE_ABGR)
def g = bufferedImage.graphics
g.drawImage(croppedImage, 0, 0, null)
g.dispose()
return bufferedImage
}
def doCrop = { inputStream, outputStream, cropSpec->
def inputBufferedImage = ImageIO.read(inputStream)
ImageIO.write(toBufferedImageWithCrop(inputBufferedImage, cropSpec), 'PNG', outputStream)
}
def inputPngFile = new File(args[0])
def outputPngFile = new File(args[1])
def params = createWidthAndHeight(new FileInputStream( inputPngFile ))
def cropSpec = [
left: (params.width * 0.1f) as int,
top: (params.height * 0.1f) as int,
right: params.width - (params.width * 0.1f) as int,
bottom: params.height - (params.height * 0.1f) as int]
def input = new FileInputStream( inputPngFile )
def output = new FileOutputStream( outputPngFile )
doCrop(input, output, cropSpec)
input.close()
output.close()
groovy image-crop white-donuts.png cropped-white-donuts.png
CropImageFilter クラスで切り抜く位置を指定してインスタンス生成します。 それを FilteredImageSource に元の画像と一緒に与えるとうまいことクロップしてくれるという処理内容です。 ちょっと難しいですね。
import java.awt.Image
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
System.setProperty("java.awt.headless", "true")
def toBufferedImage = { Image image0, Image image1->
int width = image0.width + image1.width
int height = Math.max(image0.height, image1.height)
def bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR)
def g = bufferedImage.graphics
g.drawImage(image0, 0, 0, null)
g.drawImage(image1, image0.width, 0, null)
g.dispose()
return bufferedImage
}
def doTwoInOne = { inputStream0, inputStream1, outputStream->
def inputBufferedImage0 = ImageIO.read(inputStream0)
def inputBufferedImage1 = ImageIO.read(inputStream1)
ImageIO.write(toBufferedImage(inputBufferedImage0, inputBufferedImage1), 'PNG', outputStream)
}
def inputPngFile0 = new File(args[0])
def inputPngFile1 = new File(args[1])
def outputPngFile = new File(args[2])
def input0 = new FileInputStream( inputPngFile0 )
def input1 = new FileInputStream( inputPngFile1 )
def output = new FileOutputStream( outputPngFile )
doTwoInOne(input0, input1, output)
input.close()
output.close()
groovy image-two-in-one white-donuts.png white-donuts-gray.png white-donuts-2in1.png
与えられた2つの入力画像の大きさを把握してそこから台紙となる BufferedImage の大きさを計算し、所定位置に2つの入力画像を配置したイメージインスタンスを生成し、保存する、の流れ。
こんなところでしょうか。 どれも Photoshop や GIMP でできることでしょうけど、 ときにはスクリプトの方が役立つこともあるかもしれません。