エクセルでドット絵を書くプログラムを作ってみた

エクセルでドット絵を書くプログラムを作ってみました。


エクセルのドット絵で描いた夕日
大きい画像はこちら


今回作ったプログラム


タイトルの通りです。各セルをドットのように使ってイラストを書くプログラムです。


エクセルをJavaから操作するライブラリとしてApache POI 3.8を使っています。ライブラリに同封されているJAR全部をクラスパスの通った場所において、下記のプログラムを実行すればOKです。


今回サンプルとして使った夕日の画像は、このサイトから利用させていただきました。

A cold sunset - deviantART


package main;

import java.awt.Color;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;

import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class App {

	public BufferedImage readImage(String fileName){
		BufferedImage image = null;
		try {
			image = ImageIO.read(new File(fileName));
		} catch (IOException e) {
			e.printStackTrace();
		}
		return image;
	}

	public void drawImageToWorkbook(Workbook wb, BufferedImage image, Map<Integer, CellStyle> colorToCellStyle){
		int width = image.getWidth();
		int height = image.getHeight();
		Sheet sheet = wb.getSheet(wb.getSheetName(0));

		int processCount = 0;

		for(int y = 0; y < height; y++){
			Row row = sheet.createRow(y);
			row.setHeightInPoints(CELL_HEIGHT_IN_POINTS);

			for(int x = 0; x < width; x++){
				int rgb = toGrayScale(image.getRGB(x, y));
				row.createCell(x).setCellStyle(getCellStyle(rgb, wb, colorToCellStyle));

				if(processCount != 0 && processCount % 100 == 0)
					System.out.println("processCount: " + processCount);

				processCount++;
			}

			row = null;

			// 画像のサイズが大きすぎる時は強制的に打ち切り
			if(processCount > 300000)
				break;
		}
	}

	/**
	 * フルカラーからグレースケールカラーに変換する。<br>
	 * フルカラーだと処理が遅すぎるため現実的にはグレースケールで描画する必要がある。
	 * @return
	 */
	public int toGrayScale(int rgb){
		int b = rgb & 0x000000FF;
		int g = rgb >> 8 & 0x000000FF;
		int r = rgb >> 16 & 0x000000FF;
		int avgRGB = (r + g + b) / 3;

		return (avgRGB << 16 | avgRGB << 8 | avgRGB);
	}

	public CellStyle getCellStyle(int rgb, Workbook wb, Map<Integer, CellStyle> colorToCellStyle){
		CellStyle style = null;

		if(colorToCellStyle.containsKey(rgb))
			style = colorToCellStyle.get(rgb);
		else{
			XSSFCellStyle xssStyle = (XSSFCellStyle) wb.createCellStyle();
			xssStyle.setFillPattern(CellStyle.SOLID_FOREGROUND);
			xssStyle.setFillForegroundColor(new XSSFColor(new Color(rgb)));
			colorToCellStyle.put(rgb, xssStyle);
			style = xssStyle;
		}

		return style;
	}

	public void writeWorkbook(String fileName, Workbook wb){
		FileOutputStream out = null;
		try {
			out = new FileOutputStream(fileName);
			wb.write(out);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public BufferedImage resize(BufferedImage image){
		if(image.getWidth() < MAX_IMAGE_LENGTH && image.getHeight() < MAX_IMAGE_LENGTH)
			return image;

		double width = image.getWidth();
		double height = image.getHeight();
		double scale = 1.0;

		double maxLength = Math.max(width, height);
		if(maxLength > MAX_IMAGE_LENGTH){
			scale = (double) MAX_IMAGE_LENGTH / maxLength;
		}

		int w = (int) (width * scale);
		int h = (int) (height * scale);

		BufferedImage shrinkedImage = new BufferedImage(w, h, image.getType());
		shrinkedImage.getGraphics().drawImage(
				image.getScaledInstance(w, h, Image.SCALE_AREA_AVERAGING), 0, 0, w, h, null);

		return shrinkedImage;
	}

	/** 画像の長い方の辺がこの数値を超えていたら小さくリサイズする */
	public static final int MAX_IMAGE_LENGTH = 500;

	/** in units of 1/256th of a character width<br>つまり、256を指定すると1文字分の横幅 */
	public static final int CELL_WIDTH = 256;
	/** row height measured in point size<br>仕様上、ポイント単位でしか指定できない */
	public static final int CELL_HEIGHT_IN_POINTS = 6;

	/** The numerator for the zoom magnification.<br>要は、numerator÷denominatorが最終的な倍率になる */
	public static final int SHEET_ZOOM_NUMERATOR = 1;
	/** The denominator for the zoom magnification.<br>要は、numerator÷denominatorが最終的な倍率になる */
	public static final int SHEET_ZOOM_DENOMINATOR = 5;

	public static final String IMAGE_NAME = "sunset_l.jpg";
	public static final String WORKBOOK_NAME = IMAGE_NAME + ".xlsx";

	public App() {
		Map<Integer, CellStyle> colorToCellStyle = new HashMap<Integer, CellStyle>();

		BufferedImage image = resize(readImage(IMAGE_NAME));
		System.out.println("画像の読み込み完了: " + IMAGE_NAME + ", width: " + image.getWidth() + ", height: " + image.getHeight());

		Workbook workbook = new XSSFWorkbook();
		Sheet sheet = workbook.createSheet(IMAGE_NAME);
		sheet.setZoom(SHEET_ZOOM_NUMERATOR, SHEET_ZOOM_DENOMINATOR);
		System.out.println("ワークブックの作成完了: ");

		for(int i = 0; i < image.getWidth(); i++)
			sheet.setColumnWidth(i, CELL_WIDTH);
		System.out.println("セルの横幅の設定完了: ");


		drawImageToWorkbook(workbook, image, colorToCellStyle);
		System.out.println("エクセルへの画像描画完了: ");

		writeWorkbook(WORKBOOK_NAME, workbook);
		System.out.println("エクセルファイルへの書き出し完了: " + WORKBOOK_NAME);
	}

	public static void main(String[] args) {
		new App();
	}
}


まとめ


たまにエクセルでお絵かきした系の記事が紹介されてて面白いなぁって思いますけど、プログラムで自動生成すると全然面白くないですね…。


人が一生懸命作った過程、費やした膨大な時間、人が作ったからこその不完全さとかが面白さの大事な部分なのかなー、とか思いました。


著者プロフィール
Webサイトをいくつか作っています。
著者プロフィール