Java AA→画像 (画像→AAの続き)

はじめに

前回、画像からアスキーアートを生成しましたので、 続きでアスキーアートを画像に戻します。

実装例

サンプルでは、動作確認しやすいようにmainメソッドで実行できるようにしてあります。 ソースコードは前回のものに処理を付け足す形としました("ここから追加分"の記述箇所以降)。 結果だけを確認したい場合は、この記事の一番下のリンク先で使えるようにしてありますのでご覧ください。

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.imageio.ImageIO;

/**
 *
 * @author tool-taro.com
 */
public class ImageToAscii {

    public static void main(String[] args) throws IOException, InterruptedException {

        //Javaのパス
        //String javaPath = "/usr/java/latest/bin/java";
        String javaPath = "C:\\Program Files\\Java\\jdk1.8.0_74\\bin\\java.exe";
        //jave5のディレクトリ
        String javeDir = "/usr/local/jave5";
        //jave5.jarのパス
        String javePath = "/usr/local/jave5/jave5.jar";

        //読み取りたい画像ファイルの保存場所
        String inputFilePath = "input.jpg";
        //Ascii文字数(横)
        int width = 200;

        String[] commandArray = new String[6];
        int index = 0;
        commandArray[index++] = javaPath;
        commandArray[index++] = "-jar";
        commandArray[index++] = javePath;
        commandArray[index++] = "image2ascii";
        commandArray[index++] = inputFilePath;
        commandArray[index++] = "width=" + width;

        Runtime runtime = Runtime.getRuntime();
        Process process = null;
        StringBuilder logBuilder = new StringBuilder();
        StringBuilder errorBuilder = new StringBuilder();
        int status = 0;

        try {
            //作業ディレクトリをjave5ディレクトリに移して処理する
            process = runtime.exec(commandArray, null, new File(javeDir));
            final InputStream in = process.getInputStream();
            final InputStream ein = process.getErrorStream();

            Runnable inputStreamThread = () -> {
                BufferedReader reader = null;
                String line;
                try {
                    reader = new BufferedReader(new InputStreamReader(in));
                    while (true) {
                        line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        logBuilder.append(line).append("\n");
                    }
                }
                catch (Exception e) {
                }
                finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        }
                        catch (Exception e) {
                        }
                    }
                }
            };
            Runnable errorStreamThread = () -> {
                BufferedReader reader = null;
                String line;
                try {
                    reader = new BufferedReader(new InputStreamReader(ein));
                    while (true) {
                        line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        errorBuilder.append(line).append("\n");
                    }
                }
                catch (Exception e) {
                }
                finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        }
                        catch (Exception e) {
                        }
                    }
                }
            };

            Thread inThread = new Thread(inputStreamThread);
            Thread errorThread = new Thread(errorStreamThread);

            inThread.start();
            errorThread.start();

            status = process.waitFor();
            inThread.join();
            errorThread.join();
        }
        finally {
            if (process != null) {
                try {
                    process.destroy();
                }
                catch (Exception e) {
                }
            }
        }
        System.out.format("変換結果\n%1$s", logBuilder.toString());

        //ここから追加分
        //AA→画像ファイルの保存場所
        String outputFilePath = "ascii.png";

        //標準の等幅フォントを使う
        String fontName = Font.MONOSPACED;
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Font[] fonts = ge.getAllFonts();
        for (Font font : fonts) {
            //MSゴシックがある環境であれば優先して使う
            if ("MS Gothic".equals(font.getName())) {
                fontName = font.getFontName();
                break;
            }
        }
        Font font = new Font(fontName, Font.PLAIN, 12);

        //Graphics・FontMetricsを取得するためのダミー
        BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_USHORT_GRAY);
        Graphics2D graphics = image.createGraphics();
        FontMetrics metrics = graphics.getFontMetrics(font);

        //文字間のピクセル(調整値)
        int colMargin = 0;
        //行間のピクセル(調整値)
        int rowMargin = 0;
        //等幅フォントの幅を取得する
        int fontWidth = metrics.charWidth(' ');
        //等幅フォントの高さを取得する(FontMetrics#getHeightは行間のサイズまで入ってしまうので使わない)
        int fontHeight = metrics.getDescent() + metrics.getAscent();

        String[] lines = logBuilder.toString().split("\n");
        //フォントの幅・高さから計算されるImageを生成する
        image = new BufferedImage(fontWidth * width + (width - 1) * colMargin, fontHeight * lines.length + (lines.length - 1) * rowMargin, BufferedImage.TYPE_USHORT_GRAY);
        graphics = image.createGraphics();
        //背景を白に
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
        //文字を黒に
        graphics.setColor(Color.BLACK);
        //フォントも忘れず指定
        graphics.setFont(font);
        //文字間の調整がある場合は1文字ずつ位置を決めながら描画する
        if (colMargin > 0) {
            for (int i = 0; i < lines.length; i++) {
                for (int j = 0; j < lines[i].length(); j++) {
                    //描画したい行の一番上から描画するのではなく、FontMetrics#getAscentで得られたベースラインを基準に描画する
                    graphics.drawString(String.valueOf(lines[i].charAt(j)), (fontWidth + colMargin) * j, (fontHeight + rowMargin) * i + metrics.getAscent());
                }
            }
        }
        //文字間の調整がない場合はまとめて描画する
        else {
            for (int i = 0; i < lines.length; i++) {
                //描画したい行の一番上から描画するのではなく、FontMetrics#getAscentで得られたベースラインを基準に描画する
                graphics.drawString(lines[i], 0, (fontHeight + rowMargin) * i + metrics.getAscent());
            }
        }
        ImageIO.write(image, "png", new File(outputFilePath));
    }
}

動作確認

$ javac ImageToAscii.java
$ java ImageToAscii
(AAが出力されるため省略)

変換前の画像は以下のファイルです。 c43ffd45-ca14-9958-b185-882ee7d4e40b.jpeg

今回はWindowsCentOSで実行し、フォントの違いによる差を確認しました。

Windows ascii.png

CentOS linux.png

環境

上記の実装をベースにWebツールも公開しています。 AA変換(アスキーアート生成)|Web便利ツール@ツールタロウ