Scalaでちょっとしたツールを作る

仕事で画像データ変換ツールが必要になった。
やりたいことは32bitカラーのpngファイルを16bitカラーの独自フォーマットへの変換。
元データはアルファ有無混在+アルファ情報のみの画像も有り。それらの情報も独自フォーマットのヘッダに持たせる。
処理的には、入力フォルダー中のpngファイルを読み込んでフォーマット変換、zip圧縮して出力という流れ。
普段、このようなちょっとしたツールは、ほとんどRubyで書くし実際作りかけたが挫折。
pngを読み込むgemが(windows環境だからか)依存関係でうまく動いてくれない。
この依存関係を解決できても、このツールを使うのは自分でないので相手の環境でも同じことをしなければならない。
exerbでexeにするのもハマるケースがあるのは経験済。
で、Scalaで書くことにした。
Javapngを扱うのもzip圧縮も経験済なので、Scalaで書くのはそんなに大変じゃないと予想していたけれど想像以上に楽だった。

object PngToB16 {
  import java.io._
  import java.util.zip._
  import javax.imageio._
  import java.awt.image.BufferedImage
  def main(args : Array[String]) : Unit = {
    val inDir = if (args.length > 0) args(0) else "/tmp/png/"
    val outDir = if (args.length > 1) args(1) else "/tmp/b16/"
    new File(inDir).listFiles.filter(_.getPath.endsWith(".png")).foreach{ file =>
      val bi = ImageIO.read(new FileInputStream(file))
      val rgb = ((bi.getHeight-1) to 0 by -1).map(bi.getRGB(0, _, bi.getWidth, 1, null, 0, bi.getWidth)).flatten.toArray
      val (bytes, dataType) =
        bi.getType match {
          case BufferedImage.TYPE_3BYTE_BGR =>
            (rgb.map(p => Array(((p>>5)&0xe0) | ((p>>3)&0x1f),((p>>16)&0xf8) | ((p>>13)&0x7)).map(_.toByte)).flatten ,'n')
          case BufferedImage.TYPE_4BYTE_ABGR =>
            (rgb.map(p => Array(((p)&0xf0) | ((p>>28)&0xf),((p>>16)&0xf0) | ((p>>12)&0xf)).map(_.toByte)).flatten,
            if (rgb.exists(p=>(p&0xffffff)!=0)) 'a' else 'm')
          case _ => throw new Exception("invalid png type")
        }
      println(file.getName, bi.getWidth, bi.getHeight, bi.getType, rgb.length, bytes.length)  // getType 5=BGR, 6=ABGR
      val os = new BufferedOutputStream(new FileOutputStream(outDir + file.getName.split("\\.")(0) + ".b16"))
      os.write(Array('b','1','6',dataType).map(_.toByte))
      os.write(Array(bi.getWidth & 0xff, bi.getWidth >> 8, bi.getHeight & 0xff, bi.getHeight >> 8).map(_.toByte))
      val dos = new DeflaterOutputStream(os, new Deflater(Deflater.BEST_COMPRESSION))
      dos.write(bytes, 0, bytes.length)
      dos.close
      os.close
    }
  }
}

ケースバイケースでRubyScalaを使い分けていこう。