Base64形式で受け取った画像データをBufferedImageに変換

SpringMVCの実装で用いたコードを紹介します。
トリミングやフィルターなどを用いて加工した画像データをJavaで処理して指定のフォルダにアップロードするまでの処理の備忘録です。

## JSP側での処理(JS含む)
簡単に表側(JSP)での処理のフローを書きます。

1. まず画像をアップロード。
2. Canvasに画像Imageを書き出す。
3. 書き出したCanvas上で画像をいじいじして、
4. toDataURL()メソッドを用いてBase64形式のImageURIを取り出します。

取り出したImageURIデータは頭の部分が`data:image/gif;base64,~~~~~~`のようになっています。
JavaScriptを用いて

1. `,`含めそれ以前の文字列を切り出す
2. `<form:input type="hidden" path="imageUri">`
に値をセット

※この際、JSでbyte[]に変換した上でControllerに渡してもよいのですが、なぜか不完全なbyte[]となってしまい画像が復元出来なかったのでデフォルトのBase64形式でControllerに渡しました。

それでは実際にImageUri(25万字前後の文字列)を受け取ってからの処理を書いていきたいと思います。


## Controller側での処理(説明)
###簡単な処理フロー

1. Base64形式のStringをdecodeBase64メソッドを用いてbyte[]に変換
2. byte[]をByteArrayInputStreamメソッドを用いてInputStream型に変換
3. InputStreamをImageIOクラスのreadメソッドを用いてBufferedImage型に変換

※もしかするとここまでの処理だけでImageIOクラスのwriteメソッドを用いてファイルとして書き出すことが出来るかもしれないのですが、真っ黒な画像しか生成されなかったので改めて1ピクセルずつ画像を生成する処理を今回は書きました。以下に補足のような形で書いておきます(ビット演算を用いています)。

1. 上までの処理で生成されたBufferedImage型の値から画像の幅や高さを読みとり、newする
2. BufferedImageのgetRGBメソッドを用いて1ピクセルずつのaRGB値列を取り出す
3. 取り出したRGB値列をビット演算し、a値、R値、G値、B値を算出
4. 取り出した値を再度、1ピクセルずつaRGB値列をセット

ここまでが一応BufferedImageに起こすまでの処理フローです。
最後に実際に画像として保存するまでのフローを書きます。

1. 前設定として保存するファイル名がかぶらないように現在時刻をHash化してファイル名とする
2. FileOutputStreamを用いてOutputStream型にアウトプットするファイルの保存先を設定
3. ImageIOクラスのwriteメソッドを用いてファイルに書き出す。

以上でファイル生成のフローを終わります。
それでは、以下から実際のコードを書いていきたいと思います。

### 各ファイルでの処理

まず受け取ったBase64形式のStringを
org.apache.commons.codec.binary.Base64クラスの
decodeBase64メソッドを用いてbite[]に変換します。

```java:File.java
		byte[] imageBinary = Base64.decodeBase64(ImageUri);
```

次に作成したBinaryToBufferedImageクラスを呼び込みます。

```java:File.java
		BinaryToBufferedImage BToBI = new BinaryToBufferedImage();
		BufferedImage bufImage = BToBI.aaa(imageBinary);
```

続いて作成するfile名(パス含む)の設定。
上で作成したbufImage、アップロード先のパスを含むファイル名を使用して書き出します。

```java:File.java
		OutputStream out=new FileOutputStream(uploadPath + fileName);
		ImageIO.write(bufImage, "jpg", out);
```

このFileクラス内で呼び出している自作したBinaryToBufferedImageクラスがこちら。
１行目では引数として受け取ったbyte[]をByteArrayInputStreamを用いてInputStream型に変換し、ImageIOクラスのreadメソッドでBufferedImage型に変換するということを一気にやってしまっています。
２行目は１行目で得たBufferedImage型の値を用いて再度BufferedImage型に書き起こしています。
画像をfor文で1pixelずつ書き出してRGB値をセットし直してます。
シフト演算やビット演算を用いていますが、簡単なことしかしてません。
詳しくは以下のリンクを参考にしてみてください。
[シフト演算](http://www.javaroad.jp/java_operator4.htm)
[ビット演算](http://www.javaroad.jp/java_operator5.htm)

```BunaryToBufferedImage.java
public static BufferedImage aaa(byte[] imageBinary) throws IOException {
    	BufferedImage img = ImageIO.read(new ByteArrayInputStream(imageBinary));

        int width = img.getWidth();
        int height = img.getHeight();
	    BufferedImage bufImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for(int y=0;y<height;y++){
            for(int x=0;x<width;x++){
                int c = img.getRGB(x, y);
                int r = r(c);
                int g = g(c);
                int b = b(c);
                int rgb = rgb(r,g,b);
                bufImage.setRGB(x,y,rgb);
            }
        }
		
    	return bufImage;
    }
    
    //以下、シフト演算ビット演算のメソッド
	public static int a(int c){
        return c>>>24;
    }
    public static int r(int c){
        return c>>16&0xff;
    }
    public static int g(int c){
        return c>>8&0xff;
    }
    public static int b(int c){
        return c&0xff;
    }
    public static int rgb
    (int r,int g,int b){
        return 0xff000000 | r <<16 | g <<8 | b;
    }
    public static int argb
    (int a,int r,int g,int b){
        return a<<24 | r <<16 | g <<8 | b;
    }
```

### おまけ(ファイル名Hash化のすすめ)
ここからおまけですが、現在時刻を取得してハッシュ化したものをファイル名とする処理を書いておきます。
そもそもなんでハッシュ化するかというとセキュリティな観点と保管的な観点によるものでした。

```java:File.java
public String makeFileName() {
    	Hash hash = new Hash();
		Calendar cal = Calendar.getInstance();
	    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss");
	    String strDate = sdf.format(cal.getTime());
	    String hasStringDate = hash.getSha256(strDate); //hash化
		return hasStringDate + ".jpg";
    }
```

ここで呼び出してる自作したHashクラスですが内容はこちら。
ここではSHA-256形式でハッシュ化を行っています。

```java:getSha256.java
public static String getSha256(String target) {
	    MessageDigest md = null;
	    StringBuffer buf = new StringBuffer();
	    try {
	      md = MessageDigest.getInstance("SHA-256");
	      md.update(target.getBytes());
	      byte[] digest = md.digest();
	 
	      for (int i = 0; i < digest.length; i++) {
	        buf.append(String.format("%02x", digest[i]));
	      }
	 
	    } catch (NoSuchAlgorithmException e) {
	      e.printStackTrace();
	    }
	 
	    return buf.toString();
	}
```

最後に、以下が呼び出し側のソースです。
今回はjsp側でfileUploadFormクラスのimageUriフィールド(String型)に値をセットしたのでgetで取り出します。
具体的な処理を記述したFileクラスを呼び出しインスタンス化し、fileUploadメソッドに引数でimageUriをそのまま渡してアップロードまでの処理を行わせています。
ちなみにインスタンス化した際に呼び出されるデフォルトコンストラクタの中でおまけで紹介したfile名をセットしています。

```java:MainController.java
String imageUri = fileUploadForm.getImageUri();
File file = new File();
file.fileUpload(imageUri);
```

## Controller側での処理(全コード)

```java:MainController.java
String imageUri = fileUploadForm.getImageUri();
File file = new File();
file.fileUpload(imageUri);
```

```java:File.java
package jp.co.sample_app.app.util;

import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import javax.imageio.ImageIO;

import org.apache.commons.codec.binary.Base64;

public class File {
	private String fileName ;
	
	public File() {
		fileName = makeFileName();
	}

    public void fileUpload(String ImageUri) throws IOException {
	    String uploadPath = "/usr/share/tomcat7/webapps/sample_app/resources/images/";
		byte[] imageBinary = Base64.decodeBase64(ImageUri);
		BinaryToBufferedImage BToB = new BinaryToBufferedImage();
		BufferedImage bufImage = BToB.aaa(imageBinary);
		OutputStream out=new FileOutputStream(uploadPath + fileName);
		ImageIO.write(bufImage, "jpg", out);
    }
    
    public String makeFileName() {
    	Hash hash = new Hash();
		Calendar cal = Calendar.getInstance();
	    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss");
	    String strDate = sdf.format(cal.getTime());
	    String hasStringDate = hash.getSha256(strDate); //hash化
		return hasStringDate + ".jpg";
    }

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}
}

```

```java:BinaryToBufferedImage
package jp.co.sample_app.app.util;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;

import javax.imageio.ImageIO;

public class BinaryToBufferedImage {
    public static BufferedImage aaa(byte[] imageBinary) throws IOException {
    	BufferedImage img = ImageIO.read(new ByteArrayInputStream(imageBinary));

        int width = img.getWidth();
        int height = img.getHeight();
	    BufferedImage bufImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for(int y=0;y<height;y++){
            for(int x=0;x<width;x++){
                int c = img.getRGB(x, y);
                int r = r(c);
                int g = g(c);
                int b = b(c);
                int rgb = rgb(r,g,b);
                bufImage.setRGB(x,y,rgb);
            }
        }
		
    	return bufImage;
    }
    
	public static int a(int c){
        return c>>>24;
    }
    public static int r(int c){
        return c>>16&0xff;
    }
    public static int g(int c){
        return c>>8&0xff;
    }
    public static int b(int c){
        return c&0xff;
    }
    public static int rgb
    (int r,int g,int b){
        return 0xff000000 | r <<16 | g <<8 | b;
    }
    public static int argb
    (int a,int r,int g,int b){
        return a<<24 | r <<16 | g <<8 | b;
    }
}

```

```Hash.java
package jp.co.sample_app.app.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Hash {
	/*
	   * 文字列から SHA256 のハッシュ値を取得
	   */
	public static String getSha256(String target) {
	    MessageDigest md = null;
	    StringBuffer buf = new StringBuffer();
	    try {
	      md = MessageDigest.getInstance("SHA-256");
	      md.update(target.getBytes());
	      byte[] digest = md.digest();
	 
	      for (int i = 0; i < digest.length; i++) {
	        buf.append(String.format("%02x", digest[i]));
	      }
	 
	    } catch (NoSuchAlgorithmException e) {
	      e.printStackTrace();
	    }
	 
	    return buf.toString();
	}
}

```