Friday, September 29, 2017 / Java2D, Groovy

Groovy で画像処理、普段使いのスクリプト その1( リサイズ/グレースケール/JPEG/切り抜き/2in1 )

普段ターミナル中心に bash と vim で仕事をしている身としては ちょっとした画像処理は いちいち Photoshop や GIMP を起動するより groovy スクリプトでちゃっちゃと済ませてしまいたいところです。

今回はそれら普段使いのちょっとした画像処理用コードをいろいろ書いてみます。 ここではこのとてもおいしそうな ポンシェのホワイトドーナツ 画像を例に いろいろ料理してみましょう。

white-donuts

このページにあるスクリプトの作動確認環境は以下の通りです。

groovy -version
Groovy Version: 2.4.6 JVM: 1.8.0_121 Vendor: Oracle Corporation OS: Mac OS X

画像リサイズ

100x100 のサイズの画像を生成してみます。

white-donuts-100x100

image-resize.groovy

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 インスタンスに変換しています。

グレースケール画像に変換

white-donuts-gray

image-grayscale.groovy

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 インスタンスを生成しておいてから、 そこに元の画像を描写する、という手順になります。

JPEGに変換

white-donuts

image-to-jpeg.groovy

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%分回りを切り落とす形でクロップした画像を生成してみます。

cropped-white-donuts

image-crop.groovy

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 に元の画像と一緒に与えるとうまいことクロップしてくれるという処理内容です。 ちょっと難しいですね。

2つの画像を一つの画像にする(ニコイチ)

white-donuts-2in1

image-two-in-one.groovy

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 でできることでしょうけど、 ときにはスクリプトの方が役立つこともあるかもしれません。