[Java] ZXing で QR コードを生成するメモ


# 目的

- ZXing で QRコードを生成する場合のサンプルです
- ZXing は「ゼブラ・クロッシング」と呼ぶらしいです
- 少々変わった仕様があったので、忘れないようにメモ

また「QRコード」は [(株)デンソーウェーブ](http://www.denso-wave.com/) の登録商標です

# バージョン

- ZXing Core
    - groupId : com.google.zxing
    - artifactId : core
	- version : 3.2.1
- ZXing Java SE Extensions
    - groupId : com.google.zxing
    - artifactId : javase
	- version : 3.2.1

# QRコードの画像を生成する

QRコードを画像で生成する方法は以下のような感じです。

```java
import java.awt.image.BufferedImage;
import java.nio.charset.Charset;
import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

public class QRCodeUtils {

	public static BufferedImage create(String contents, ErrorCorrectionLevel level, Charset charset, int size)
			throws WriterException {
		Objects.requireNonNull(contents);
		Objects.requireNonNull(level);
		Objects.requireNonNull(charset);
		if (!charset.canEncode()) {
			throw new IllegalArgumentException("cannot encode: " + charset.displayName());
		}

		Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
		hints.put(EncodeHintType.ERROR_CORRECTION, level);
		hints.put(EncodeHintType.CHARACTER_SET, charset.displayName());
		hints.put(EncodeHintType.MARGIN, 4);

		QRCodeWriter writer = new QRCodeWriter();
		BitMatrix matrix = writer.encode(contents, BarcodeFormat.QR_CODE, size, size, hints);
		BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(matrix);
		return bufferedImage;
	}
}
```

画像の色を変更したい場合には `MatrixToImageWriter.toBufferedImage()` の第２引数に `MatrixToImageConfig` を指定することで可能です。

```java
        // 青と白で表示
		MatrixToImageConfig config = new MatrixToImageConfig(0xff0000ff, 0xffffffff);
		BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(matrix, config);
```

画像ではなく、文字列でもよければ `BitMatrix#toString()` が使えます。

# ヒント情報(EncodeHintType)

QRコードを作る際に、いくつかヒント情報を指定することができます。
ヒント情報は `Map` で指定します。
キーは列挙型の `EncodeHintType` を使用しますが、キーによって値の型が異なるため `Map<EncodeHintType, Object>` で定義します。

また、キーが列挙型なので `EnumMap` が使用できます。

## 誤り訂正レベル

誤り訂正レベルは以下のように指定します。

- キー : `EncodeHintType.ERROR_CORRECTION` 
- 値 : 列挙型の `ErrorCorrectionLevel` で指定する
    - `ErrorCorrectionLevel.L` : 7%まで訂正可能(デフォルト)
    - `ErrorCorrectionLevel.M` : 15%まで訂正可能
    - `ErrorCorrectionLevel.Q` : 25%まで訂正可能
    - `ErrorCorrectionLevel.H` : 30%まで訂正可能

ちなみに、QRコードにイラストなどを載せても読み取れるのはこの誤り訂正能力のおかげですが、QRコード開発元のデンソーウェーブでは以下のように言っています。ご注意ください。

http://www.qrcode.com/faq.html
QRコードについて「10.色を付けたりイラストを入れるような使い方をしても問題ありませんか？」

> QRコードにイラストを重ねたり、デザインをのせて変形してしまうと、ちょっとした汚れや欠けでも読み取り出来なくなったり、読み取りの反応が悪くなってしまうことがありますので注意が必要です。
安定した読み取りという面から、JIS、ISOの規格で制定されている内容に従ってご利用いただくことを推奨しております。
また、QRコードにイラストを重ねたりデザインを乗せるということは、QRコードの規格から外れ「QRコードではないもの」となってしまう可能性がございます。デンソーウェーブは、JIS、ISOの規格に沿ったQRコードに限っては特許権を行使しませんが、規格を逸脱したQRコードについては、この限りではございませんので、デンソーウェーブは特許権を行使させていただくこともございます。
QRコードにイラストやデザインを施すような使い方をご検討されている場合は、事前にデンソーウェーブまでご相談ください。

## 文字セット

文字セットは以下のように指定します。

- キー : `EncodeHintType.CHARACTER_SET`
- 値 : 文字列( `String` )で文字セット名を指定
    - デフォルトは `"ISO-8859-1"`

デフォルトは上記のように `"ISO-8859-1"` (いわゆる ISO-Latin-1) なので日本語が扱えません。必要であれば日本語が扱える文字セットを指定してください。

後述のように `"Shift_JIS"` の文字セット名は特別な対応があります（詳細は「モード」を参照）。

## マージン

マージンは以下のように指定します。

- キー : `EncodeHintType.MARGIN`
- 値 : 上下左右のマージンをセル数( `Integer` )で指定
    - デフォルトは `4`

QRコードには、周囲に４セル以上のマージンを必要とします。普通はデフォルトのままで十分だと思います。
もし、表示する際に画像の周りに余白を別途用意するなら、マージンには `0` を指定しても大丈夫です。

# モードやバージョンを取得する

QRコードには、「モード」や「バージョン」といった情報がありますが、基本的に自動的に判定されます。
上記の方法では、実際に適用された「モード」や「バージョン」は取得できません。

もしなんらかの理由で「モード」や「バージョン」が必要な場合（例えば、「バージョン」によって画像のサイズを変えるなど）は `Encoder` を使用します。

```java
package net.the_blue_pla.net.qrcode;

import java.nio.charset.Charset;
import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;

import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.encoder.Encoder;
import com.google.zxing.qrcode.encoder.QRCode;

public class QRCodeUtils {

	public static QRCode createQRCode(String contents, ErrorCorrectionLevel level, Charset charset)
			throws WriterException {
		Objects.requireNonNull(contents);
		Objects.requireNonNull(level);
		Objects.requireNonNull(charset);
		if (!charset.canEncode()) {
			// エンコード不可
			throw new IllegalArgumentException("cannot encode: " +
					charset.displayName());
		}

		Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
		hints.put(EncodeHintType.CHARACTER_SET, charset.displayName());

		QRCode code = Encoder.encode(contents, level, hints);
		return code;
	}
}
```

※ `MatrixToImageWriter` を使っていないので「ZXing Java SE Extensions」は不要。

## com.google.zxing.qrcode.encoder.QRCode

`QRCode` は、以下の情報を持っています。

- `QRCode#getVersion()` - バージョン
- `QRCode#getECLevel()` - 誤り訂正レベル
- `QRCode#getMode()` - モード
- `QRCode#getMatrix()` - QRコードの白黒情報

`QRCode` から画像データを直接作るメソッドなどは用意されていませんが `QRCode#getMatrix()` から必要な情報は取得可能です。

`QRCode` から独自に画像を作るなら `QRCodeWriter` と `MatrixToImageWriter` が参考になります。

（面倒なら、かつ性能的に問題なければ `QRCodeWriter` を使えばいいですが）

## バージョン

バージョンとは、QRコードのセル数の大きさを表します。
一番小さい「バージョン１」は 21セル×21セルです。
バージョンが１つ増える毎に縦横ともに4セルずつ増えていきます。
最大の「バージョン40」は177セル×177セルになります。

| バージョン | 縦横のセル数 |
|:--:|:--:|
| バージョン１ | 21セル×21セル |
| バージョン２ | 25セル×25セル |
| … | … |
| バージョンn | (17+4*n)セル×(17+4*n)セル |
| … | … |
| バージョン40 | 177セル×177セル |

## モード

QRコードのモード( `com.google.zxing.qrcode.decoder.Mode` )には、以下のものがあります。

| モード | 許容される文字 |
|:--:|:--|
| 数字( `NUMERIC` ) | 数字のみ (0～9) |
| 英数字( `ALPHANUMERIC` ) | 数字、英大文字と一部の記号 (0～9, A～Z, スペース, '$', '%', '*', '+', '-', '.', '/', ':') |
| 漢字( `KANJI` ) | Shift_JISの２バイトコードのみ |
| バイト( `BYTE` ) | 8bitのデータすべて |

他にも「混在モード」があるはずですが、ZXing 3.2.1 ではサポートされていないようです（たぶん）。

モードについては、ちょっと変わった判定方法になっています。
指定した文字セットが `"Shift_JIS"` かそうでないかで判定方法が異なります。

### 文字セットが "Shift_JIS" 以外の場合の判定

文字セットが  `"Shift_JIS"` でない場合には、以下のようになっています。

- 対象のデータが数字のみの場合 ⇒ 数字モード
- 対象のデータが英数字と一部の記号のみの場合 ⇒ 英数字モード
- それ以外のデータが含まれる ⇒ バイトモード

英数字モードでの英字は英大文字のみです。
英小文字があるとバイトモードになってしまいます。

## 文字セットが "Shift_JIS" の場合の判定

文字セットが `"Shift_JIS"` の場合には、以下のようになっています。

- 対象のデータが  `"Shift_JIS"` の2バイト文字のみの場合 ⇒ 漢字モード
- それ以外のデータが含まれる ⇒ バイトモード

つまり、文字セットとして `"Shift_JIS"` を指定した場合には、対象データが数字だけであってもバイトモードになるようです。

また、この判定は `"Shift_JIS"` を指定した場合のみの動作です。
例えば別名の `"SJIS"` や、 `"shift_jis"` のように小文字で指定した場合は、上記「文字コードが "Shift_JIS" 以外の場合の判定」が使われます。

当然ですが `"MS932"` や `"Windows-31J"` についても、上記「文字コードが "Shift_JIS" 以外の場合の判定」が使われます。そのため漢字モードでは「NEC特殊文字」「NEC選定IBM拡張文字」「IBM拡張文字」「ユーザ外字」などは使えません。

参考：[[Java] シフトJISの扱い](http://qiita.com/yoshi389111/items/9060c8b62df7cac31de9)

ちなみに、このあたりの判定は `com.google.zxing.qrcode.encoder.Encoder` で行っています。

```java:Encoder.java
// （略）
  private static Mode chooseMode(String content, String encoding) {
    if ("Shift_JIS".equals(encoding)) {
      // Choose Kanji mode if all input are double-byte characters
      return isOnlyDoubleByteKanji(content) ? Mode.KANJI : Mode.BYTE;
    }
    // （略）
```

【追記 2015-12-03】

> つまり、文字セットとして `"Shift_JIS"` を指定した場合には、対象データが数字だけであってもバイトモードになるようです。

この動作はおかしいような気がしたので [拙い英語で pull request を送ってみた](https://github.com/zxing/zxing/pull/508) ところ、マージしていただけました。
（pull request を送って、数分で返事が、さらに数分でマージされました。素早い！）
今後の版では `"Shift_JIS"` でも `NUMERIC` / `ALPHANUMERIC` に判定されると思います。