最近Java2Dでちょっとした画像加工処理を行う機会があったのですが、意外と日本語の情報が見当たらなかったものがあったので備忘録的なメモです。今更こんな古いAPIの話を?とも思いますが、こういう基礎的なAPIはいつになっても使うものですし。
やることは既存の画像に対して文字を重ね合わせて出力するというものです。それも縁取りのある文字列を描画します。
次のような画像に対して...

次のように文字列を重ねます。

見やすくするために文字列は縁取りを付けて描画します。これが今回のポイントです。
Java2Dの java.awt.Graphics2D クラスには drawString() メソッドがありますが、このメソッドでは縁取り文字列を描画することができません。
縁取り文字列を描画するには描画しようとする文字列のグリフ情報を保持する java.awt.font.GlyphVector クラスを利用します。
次のように java.awt.Font インスタンスと文字列から GlyphVector インスタンスを作ります。
// 加工元の画像ファイルに対するGraphics2Dオブジェクトの取得 var image = ImageIO.read(Objects.requireNonNull(Java2DSample.class.getResourceAsStream("/java2dtest.png"))); var graphics = (Graphics2D) image.getGraphics(); // GlyphVectorの取得 var font = new Font(Map.of( TextAttribute.FAMILY, Font.SANS_SERIF, TextAttribute.SIZE, 80, TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD)); var glyphVector = font.createGlyphVector(graphics.getFontRenderContext(), "Outlined Text");
Glyphvector の getOutline() メソッドをコールすることで当該グリフを描画するための java.awt.Shape オブジェクトを取得します。引数には描画座標を指定します (graphics2D#drawString() と同じく座標はベースラインの左端の文字位置) 。
var outlineShape = glyphVector.getOutline(120.0f, 980.0f);
あとはこの Shape を使って輪郭と塗りつぶしをそれぞれ描画することになります。滑らかに描画するためにアンチエイリアスをオンにすることをお勧めします。
// 滑らかに描画するためにアンチエイリアスをオンにし、品質重視のレンダリングヒントを与えるのがお勧め graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // 輪郭の描画 graphics.setStroke(new BasicStroke(10.0f)); graphics.setColor(Color.BLACK); graphics.draw(outlineShape); // 塗りつぶし graphics.setColor(Color.WHITE); graphics.fill(outlineShape);
これで先に示したように縁取り文字列を描画することができます。
JavaFXだとCSSで指定できるのでこの辺りは楽ですね。でもJavaFXは今回のようにアプリを作らず純粋に画像を操作するだけの目的には使えないんですよね。
今回のサンプルコードの全体は以下のgistにアップしてあります。