Posts tagged ‘Graphic2D’

Graphics2Dに文字列を描画

Flashでやったらいいじゃないかとは思いますが、画像ファイルに文字列を描画したい場合があります。
単一行の文字列であれば特に何も考えず、
Graphics2D#drawString
すれば良いです。(参考:drawStringメソッド

しかし複数行にわたる文字列を描画するためには、前述のメソッドでやるには面倒くさい。
現在のy座標の位置と、フォントの高さを計算してさらにどこで文字列を折り返すかもあらかじめ決めておかないと難しい。

そこで、LineBreakMeasurer
というクラスを使用して描画すると多少は簡単になる。
正直、このJavaDocをはじめて見たときはまったく理解出来なかったのですが、
実際やってみると色々わかったことが出てきたのでメモしておきます。

つまりLineBreakMeasurerは、指定した幅に指定した文字列が入りきらなければ(または文の教会と判断したならば)
勝手に行に分割してくれるというものだと思います。

LineBreakMeasurerのコンストラクタに渡すのは

の三つを抑えておけば大丈夫そうです。
AttributedStringには、描画する文字情報を保持させます。TextAttribute
に設定出来る情報があります。
BreakIteratorには、文を分割する境界に関する情報を持たせます。
LineBreakMeasurerでこれを指定しないコンストラクタを使用すると、BreakIterator#getLineInstance()
が使用されるようです。文字を一杯一杯に出したいときは、BreakIterator#getCharacterInstance()
を使用すると良いです。
FontRenderContextは、Graphics2D#getFontRenderContext()
を指定しておけば良いです。実際どう効いてくるのかは未調査です。

行分割出来たらセンタリングと右寄せとか位置を調整したくなってくるのですが、
フォントアセント、フォントディセント、レディング等を考えないといけないのですが、
(アセント、ディセント等はフォントのコンセプトが割と分かりやすいです。)
とりあえず、いいサンプルがあったのでそれを参考にします。

LineBreakMeasurer: getPosition()

LineBreakMeasurer#nextLayout(float)
を呼ぶと次の分割単位(ここでは行を想定)を返します。
行が変わり、描画直前でアセントを設定し、描画し終わったらディセント、レディングを追加します。

ただし、ここまでは良かったのですが、垂直の位置をセンタリングや下寄せにしたい場合はどうしようか?
という問題が出てきました。現状のアイデアとしては、LineBreakMeasurerを最後の行から上に向かって描けば良さそうですがまだ未検証。

こちらも参考にさせていただきました。
Javaで複数行の文字列をレンダリングするときの注意点


gif画像のリサイズ

透過色のあるgif画像の出力
で、透過色付きgif画像の出力は出来たが、出力したgifファイルをリサイズする必要が出てきました。

これを実現するには、アフィン変換(アフィン写像参照)という考え方を使って行うらしい。
アフィーンというかギャフンという感じだ。

実装はAffineTransformOpを使って元イメージをフィルタさせるらしい。

このクラスは、アフィン変換を使用して、ソースのイメージまたは Raster の 2 次元座標からデスティネーションのイメージまたは Raster の 2 次元座標への線形マッピングを実行します。使用される補間のタイプは、コンストラクタを介して、RenderingHints オブジェクトまたはこのクラスで定義されている整数型補間タイプのうちの 1 つによって指定されます。

それにしてもいつもすごく分かりにくい説明ですね。

ちなみに、java.awt.imageパッケージのxxxxOpというクラスは、操作対象があって、その対象に対しての操作が出来るモノらしいです。
AffineTransformOpは、文字通りアフィン変換を対象に対して行うものです。

アフィン変換にはいくつか種類があって、リサイズということは拡大縮小(スケーリング)なので、その内容を指定して
AffineTransformを引数として
AffineTransformOpに渡します。

AffineTransformは、スケーリング変換の場合AffineTransform#getScaleInstance
取得できますが、この引数はリサイズ後のサイズではなく、元画像とリサイズ後画像の比率(リサイズ後画像/元画像)を指定します。

	//比率を出す
	double resizeWidthRatio = (double)resizeWidth / (double)src.getWidth();
	double resizeHeightRatio = (double)resizeHeight / (double)src.getHeight();
 
	AffineTransform at = AffineTransform.getScaleInstance(resizeWidthRatio, resizeHeightRatio);
	AffineTransformOp atOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC);

このAffineTransformOpの第二引数には以下の3つのどれかを指定します。

バイキュービック法によると、TYPE_BICUBICを選択するのが画質が良さそう。

しかし、これでgifファイルを書き込みしても透過されてくれないので、自力でやる必要がある。
記事が埋もれそうなので次回に。


透過色のあるgif画像の出力

gifイメージの出力の続きです。

「透過色」「gif」「java」で検索してもそのものの答えが出なかったのですが、
今日、ついに決着がついたのでメモしておきます。

1. BufferedImageの生成

BufferedImageはBufferedImage.TYPE_BYTE_INDEXEDで作成します。
カラーモデルを指定するコンストラクタです。

BufferedImage(int, int, int, java.awt.image.IndexColorModel)

ですね。第4引数のIndexColorModelは

BufferedImage(int, int, int)
のソースのTYPE_BYTE_INDEXEDの所から引用させていただいて、以下のように作成しました。

private IndexColorModel createIndexColorModel(){
        // Create a 6x6x6 color cube
        int[] cmap = new int[256];
        int i=0;
        for (int r=0; r < 256; r += 51) {
            for (int g=0; g < 256; g += 51) {
                for (int b=0; b < 256; b += 51) {
                    cmap[i++] = (r<<16)|(g<<8)|b;
                }
            }
        }
        // And populate the rest of the cmap with gray values
        int grayIncr = 256/(256-i);
 
        // The gray ramp will be between 18 and 252
        int gray = grayIncr*3;
        for (; i < 256; i++) {
            cmap[i] = (gray<<16)|(gray<<8)|gray;
            gray += grayIncr;
        }
 
        return new IndexColorModel(8, 256, cmap, 0, true, 215,
                                         DataBuffer.TYPE_BYTE);
	}

元ソースと違うところは、最後のIndexColoeModelを生成する所で、第5引数(hasAlpha)にtrue、
第6引数に透過色とする為のインデックス番号を指定している所です。
(IndexColorModelのコンストラクタIndexColorModel(int, int, int[], int, boolean, int, int)

処理の最初のほうでcmapという変数に色を詰め込んでいますが、この場合は215番目が白(0xffffff = 16777215)になります。
白を透過色とする場合です。
これはコンストラクタかユーティリティを用意して欲しいところですね。

2. Graphics2Dの生成

そのBufferedImageからGraphics2Dを生成します。
生成して色々設定していきますが、gif出力の場合、描画のアンチエイリアス設定をすると白背景にしたつもりが真っ黒になってしまいます。
pngで出力する場合は描画のアンチエイリアス設定をしても問題ありません。

	BufferedImage b = new  BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, createIndexColorModel());
	Graphics2D g = b.createGraphics();
	Map renderingHints = new HashMap();
	//描画のアンチエイリアス
//		renderingHints.put(RenderingHints.KEY_ANTIALIASING,
//				RenderingHints.VALUE_ANTIALIAS_ON);
	//文字描画のアンチエイリアス
	renderingHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
			RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
	//描画ヒントキー
	renderingHints.put(RenderingHints.KEY_RENDERING,
			RenderingHints.VALUE_RENDER_QUALITY);
	//アルファ補完
	renderingHints.put(RenderingHints.KEY_ALPHA_INTERPOLATION,
			RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
	g.setRenderingHints(renderingHints);
 
	g.setComposite(AlphaComposite.Clear);
	g.fillRect(0, 0, width, height);
	g.setComposite(AlphaComposite.SrcOver);
		:

また、AlphaCompositeのClearを設定、塗りつぶし、AlphaCompositeのSrcOver設定を順番にやります。
この後に内容を描画していきます。

3. gifファイル出力

private void writeImage(String outputFile, BufferedImage image) {
	File file = new File(outputFile);
	ImageOutputStream ios = null;
	ImageWriter iw = null;
	try {
		ios = ImageIO.createImageOutputStream(file);
		iw = ImageIO.getImageWritersByFormatName("gif").next();
 
		iw.setOutput(ios);
		iw.write(image);
 
	} catch (IOException e) {
		throw new Exception("ファイル作成に失敗しました");
	} finally {
		iw.dispose();
		try {
			ios.close();
		} catch (IOException e) {
			throw new Exception("ファイルのClose処理に失敗しました");
		}
	}
}

描画内容の色が白にあまりにも近い場合(今回は0xfffffeで試した)ら、白に滅色されてしまっているのか、
出力されませんでした。
可能であればWebセーフカラーにあわせておいたら確実でしょう。