久しぶりにデバッグ画像をプログラムから生成する必要が生じたので、Java2D による画像生成を復習した。
いつも通り main.kt にコードを書いていきます。
import java.io.File
import java.awt.Color
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
fun main(){
System.setProperty("java.awt.headless", "true")
val message = "Hello, World!"
val pngFile = File("hello-world.png")
val w = 300
val h = 200
val img = BufferedImage(w,h, BufferedImage.TYPE_4BYTE_ABGR)
val g = img.getGraphics()
g.setColor(Color.WHITE)
g.fillRect(0, 0, w, h)
val fm = g.getFontMetrics()
val labelW = fm.stringWidth(message)
val labelH = fm.getHeight()
val ascent = fm.getAscent()
val descent = fm.getDescent()
val x = (w - labelW)/2
val y = (h - labelH)/2 + (ascent+descent)
g.setColor(Color.BLACK)
g.drawString(message, x, y)
g.dispose()
ImageIO.write(img, "PNG", pngFile)
}
ビルドと実行:
$ kotlinc main.kt -include-runtime -d main.jar
$ java -jar main.jar
macOS の環境(適当)では以下のようになりました。
では次は日本語表記にしてみます。 適切なフォントを選択しましょう。
使用できるフォントを調べるにはこれ:
val fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()
fonts.forEach { font->
println(font.name)
}
たくさんありますが、HiraginoSans-W6 を使ってみます。
このフォントを使って描画するには、g.setFont() すればOKです。 まず、このフォント名でフォントオブジェクトを取得できる関数を記述。
val findFontByName: (String)-> Font = { fontName->
val fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()
val targetFonts = fonts.filter { it.name == fontName }
if( targetFonts.size>0 ){
targetFonts[0]
}
else {
fonts[0]
}
}
使う時は findFontByName("HiraginoSans-W6") とします。
val font = findFontByName("HiraginoSans-W6")
val myFont = Font(font.name, font.style, 32)
g.setFont( myFont )
あとは、Graphics インスタンスに setFont するだけです。 ちなみにフォントサイズは 32 にしました。(フォントサイズの単位がわからない、ポイントなんだろうか?)
コード全体を確認します。
import java.io.File
import java.awt.image.BufferedImage
import java.awt.Font
import java.awt.Color
import java.awt.GraphicsEnvironment
import javax.imageio.ImageIO
val findFontByName: (String)-> Font = { fontName->
val fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()
val targetFonts = fonts.filter { it.name == fontName }
if( targetFonts.size>0 ){
targetFonts[0]
}
else {
fonts[0]
}
}
fun main(){
System.setProperty("java.awt.headless", "true")
val message = "こんにちは、世界!"
val pngFile = File("hello-world.png")
val w = 300
val h = 200
val img = BufferedImage(w,h, BufferedImage.TYPE_4BYTE_ABGR)
//println(img)
val g = img.getGraphics()
g.setColor(Color.WHITE)
g.fillRect(0, 0, w, h)
val font = findFontByName("HiraginoSans-W6")
val myFont = Font(font.name, font.style, 32)
g.setFont( myFont )
val fm = g.getFontMetrics()
val labelW = fm.stringWidth(message)
val labelH = fm.getHeight()
val ascent = fm.getAscent()
val descent = fm.getDescent()
val x = (w - labelW)/2
val y = (h - labelH)/2 + (ascent+descent)
g.setColor(Color.BLACK)
g.drawString(message, x, y)
g.dispose()
ImageIO.write(img, "PNG", pngFile)
}
Hello, World! から、日本語メッセージ「こんにちは、世界!」に変更しています。
さすがにヒラギノは綺麗ですね。
ここまでは macOS でしたが、Linux で実行する場合は、Linux 側で日本語に対応したフォントを指定する必要があります。
そのまま実行すると文字化けしてしまいます。
$ fc-list | grep IPA
などとして日本語フォントが入っているか確認。試した環境では入っていなかったので、インストール。
$ sudo apt install fonts-ipaexfont
フォントインストール後に再度 fc-list を実行すると:
$ fc-list | grep IPA
/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf: IPAexGothic,IPAexゴシック:style=Regular
/usr/share/fonts/opentype/ipaexfont-mincho/ipaexm.ttf: IPAexMincho,IPAex明朝:style=Regular
/usr/share/fonts/truetype/fonts-japanese-mincho.ttf: IPAexMincho,IPAex明朝:style=Regular
/usr/share/fonts/truetype/fonts-japanese-gothic.ttf: IPAexGothic,IPAexゴシック:style=Regular
うまくいきました。
Java上で、フォント名を調べると IPAexGothic, IPAexMincho が追加されていた。
あとは、いままで
val font = findFontByName("HiraginoSans-W6")
していた部分を
val font = findFontByName("IPAexGothic")
などと記述すればよい。
描画が荒い! レンダリングヒントを設定して美しくしましょう。
import 追加:
import java.awt.RenderingHints
import java.awt.Graphics2D
高品質なレンダリングヒントを生成する関数を追加:
val createHighQualityRenderingHint: ()->RenderingHints = {
val hints = RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON)
hints.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY)
hints
}
このレンダリングヒントを g にセットすればよいのですが、 この g こと Graphics インスタンスは setRenderingHints メソッドが存在しないので Graphics2D インスタンスにキャストしておく必要があります。( as Graphics2D の部分 )
//val g = img.getGraphics()
val g = img.getGraphics() as Graphics2D
g.setRenderingHints( createHighQualityRenderingHint() )
これで実行すると、美しく描画できました。
最後に Makefile を書いておきます。
run: main.jar
java -jar main.jar
main.jar: main.kt
kotlinc main.kt -include-runtime -d main.jar
clean:
$(RM) main.jar