JavaにQRGenというZXingをいい感じにラップしたQRコード作成用ライブラリがあったので試してみた

# はじめに

JavaでQRコードを作成するライブラリと言えば、Google製のZXingがよく導入されている印象ですが、
実際使ってみるとソースコードが割りと低レイヤな感じになるところがちょっと気になるなと思ってました。

ざっくり書くとこんな感じです。

```java:ZXingを使用してQR画像を生成する際のソースコード
BitMatrix matrix = new QRCodeWriter().encode(
    "https://qiita.com/nimzo6689", BarcodeFormat.QR_CODE, 320, 320
);
BufferedImage qrCodeImage = MatrixToImageWriter.toBufferedImage(matrix);
ImageIO.write(qrCodeImage, "png", Files.newOutputStream(Paths.get("qrcode.png")));
```

ただ、実際はもっと複雑になるケースがほとんどで、
例えば、余白を調整したり、文字コードを変えたり、白黒ではなく別の色に変更したり、などなど。
あと、伸ばしても画質が下がらないようにSVGファイルとして出力したいケースもあると思います。

上記のことを実現しようとすると、余計レイヤーの低いコードが増えるわけですが、
ZXingをラップして作成された[QRGen](https://github.com/kenglxn/QRGen)というライブラリを使えば、
メソッドチェーンで簡単にQR画像を生成する処理を書くことができるということを最近知ったので、
ちょっと使い方についてまとめてみました。

# インストール方法

基本的にはMaven、Gradleなどのパッケージ管理ツールにいつものように
dependencyを追加することで利用可能となります。
ただ、一点注意すべきポイントがありまして、バージョン2.1.0以降は、
Maven Centralにはリリースをしていないため、
[jitpack.io](https://jitpack.io/#kenglxn/QRGen)をリポジトリに追加しておく必要があるとのことです。

公式ページに載っている通りですが、Gradleだと以下のようになります。

```groovy:build.gradle
apply plugin: 'java'

sourceCompatibility = 1.12

repositories {
    maven {
        url "https://jitpack.io"
    }
}

dependencies {
    // JavaSEで利用する場合。AndroidだとartifactIdがandroidになります。
    compile 'com.github.kenglxn.QRGen:javase:2.6.0'
}
```

ZXingだとcoreとjavase、または、androidと依存性を2つ書く必要がありますが、
QRGenだと一つで済むので少し楽できます。

# 使用例

ローカルで試したソースコードを以降載せていきます。
なお、QRGenは名前の通り、QRコードを生成する専用のライブラリのため、
スキャンしたい場合はZXingのAPIを利用する必要があるようです。

なお、本記事で載せているコードは以下の環境で検証しました。

Windows10 Pro, Java12(AdoptOpenJDK with HotSpot), IntelliJ IDEA 2019
※マイナーバージョン以降は 2019/08/07 付けで最新版のもの。

記事内のサンプルコードはimport文等を省略しているため、ファイル全体を確認したい場合は[Gist](https://gist.github.com/nimzo6689/83fbd4ad4c24276fe0e8ea24e9ffe6f1)をご参照ください。

また、生成したQR画像は[こちら](https://play.google.com/store/apps/details?id=com.google.zxing.client.android&hl=ja)のAndroidアプリで読み取れることを確認しました。

## Fileオブジェクトを生成して出力

まずは、Fileオブジェクトで作成する方法です。

`QRCode#file`でtempディレクトリに`QRCode.png`という画像ファイルが作成されるので、
それを本来出力したいパスへ移動しています。

同じファイル名で何回でも書き込めるようにするために`REPLACE_EXISTING`を指定しました。

```java:QRGenToFile.java
File qrCode = QRCode.from("https://qiita.com/nimzo6689").file();
Files.copy(qrCode.toPath(), Paths.get("qrcode.png"), StandardCopyOption.REPLACE_EXISTING);
```

冒頭で書いたサンプルコードと同じ内容のものですが、かなりシンプルになり、
ZXingのAPIを把握する必要もないので、実装のしやすさや可読性が高まりますね。

実際に生成できたQR画像はこちらです。
サイズは何も指定しなければ、125x125になるようです。

![qrcode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/2863ba89-801d-5de8-022e-187b9baefa8b.png)

## ByteArrayOutputStreamオブジェクトを生成して出力

続いて、ByteArrayOutputStreamオブジェクトを作成する方法です。

```java:QRGenToBaos.java
try (ByteArrayOutputStream qrCode = QRCode.from("https://qiita.com/nimzo6689").stream();
        OutputStream outputStream = Files.newOutputStream(Paths.get("qrcode.png"))) {

    qrCode.writeTo(outputStream);
    qrCode.flush();
}
```

`QRCode#file`と比べてコード量が多くなってしまいますが、
例えば、`ImageIO.read(new ByteArrayInputStream(qrCode.toByteArray()))`などで
`BufferedImage`を生成して何かしらの画像処理を行う場合はこちらが便利そうです。

あと、tempファイルを作成することもないので、パフォーマンスもこちらの方がよさそうではあります。

## 生成済みのOutputStreamオブジェクトに対して出力

`QRCode#stream`では新しいOutputStreamを生成していましたが、
既に生成済みのOutputStreamにQR画像を書き込みたい場合は`QRCode#writeTo`が使えます。

```java:QRGenToExistingOs.java
try (OutputStream os = Files.newOutputStream(Paths.get("qrcode.png"))) {
    QRCode.from("https://qiita.com/nimzo6689").writeTo(os);
    os.flush();
}
```

`QRCode#stream`よりも記述量は少なくて済みます。

## いろいろ設定値を指定して出力

生成するQR画像の画像形式やサイズ、色を指定できます。
また、ZXingで[`EncodeHintType`](https://zxing.github.io/zxing/apidocs/com/google/zxing/EncodeHintType.html)で指定していた設定内容もQRCodeオブジェクト経由で設定可能です。

```java:QRGenWithHint.java
File qrCode = QRCode.from("https://qiita.com/nimzo6689")
        .to(ImageType.GIF)
        .withSize(320, 320)
        .withColor(0xFF6876B4, 0xFFF7F4F7)
        .withCharset("UTF-8")
        .withErrorCorrection(ErrorCorrectionLevel.L)
        .withHint(EncodeHintType.MARGIN, 2)
        .file();

Files.copy(qrCode.toPath(), Paths.get("qrcode.gif"), StandardCopyOption.REPLACE_EXISTING);
```

`withColor`で指定している値は透明度（2 bytes）+ RGB（6 bytes）の形式となります。

生成したQR画像はこちらです。

![qrcode.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114728/b93ec407-1871-8eb1-91bd-702d79ad19de.gif)

## svgファイルとして出力

SVG変換はJavaSEのみ利用可能です。
Androidは利用不可ですが、代わりにファイル生成なしでImageViewにQR画像を表示できる
`QRCode#bitmap`が利用可能だそうです。（今回は未検証）

```java:QRGenToSvg.java
File qrCode = QRCode.from("https://qiita.com/nimzo6689").svg();
Files.copy(qrCode.toPath(), Paths.get("qrcode.svg"), StandardCopyOption.REPLACE_EXISTING);
```

生成した`qrcode.svg`はChrome等で開くと適切にレンダリングしてくれます。

QR画像の画質に拘る場合や大きな紙で引き伸ばして印刷したい場合に
サイズの大きいQR画像を準備する必要がなくなるので助かりますね。

## いろいろなスキーマを試す

QR画像はURLのほかにメアドや位置情報、Wifiなどに使われていますが、
そういったデータ形式ごとの情報を保持するSchemaオブジェクトもいくつか提供されています。

`README`では全部で16個のスキーマがサポートされているようですが、
ソースコード見ると、実際はもう少し多いようです。
`KddiAu`とかもあるんですね。~~意識高すぎ！~~

### Urlスキーマ

WebページのURLを保持するスキーマです。
http、または、httpsから始まる文字列が格納できます。

あえて、このオブジェクト経由で`QRCode::from`に食わせる必要はない気がします。

```java:QRGenForUrl.java
Url url = Url.parse("https://qiita.com/nimzo6689");
File qrCode = QRCode.from(url).file();
Files.copy(qrCode.toPath(), Paths.get("qrcode.png"), StandardCopyOption.REPLACE_EXISTING);
```

### Emailスキーマ

メールアドレスを保持するスキーマです。
`mailto:メールアドレス`の書式の文字列を格納できます。

一応、`new EMail("mailto:taro.sato@example.org")`という書き方もできますが、
これだと`mailto:`から始まる文字列かどうかといった検証処理が行われないので、
基本的には`EMail::parse`を使うのが適切だと思います。

```java:QRGenForEmail.java
EMail email = EMail.parse("mailto:taro.sato@example.com");
File qrCode = QRCode.from(email).file();
Files.copy(qrCode.toPath(), Paths.get("qrcode.png"), StandardCopyOption.REPLACE_EXISTING);
```

読み取って起動するアプリにGmailを選択すると、ToにQR画像で埋め込んだメールアドレスが入力されていました。

### GeoInfoスキーマ

位置情報を保持するスキーマです。
`geo:latitude,longitude`の書式の文字列を格納できます。

QR画像はだいたいWebサイト配布ではなく、店頭などからWeb情報にアクセスしたいときに使われるので、
いまいち位置情報のQR画像を使用する場面が想像しにくいです。
使いどころが気になります。

```java:QRGenForGeoInfo.java
GeoInfo geoInfo = GeoInfo.parse("geo:35.6761775,139.7173954");
File qrCode = QRCode.from(geoInfo).file();
Files.copy(qrCode.toPath(), Paths.get("qrcode.png"), StandardCopyOption.REPLACE_EXISTING);
```

検証で使用したQRコードリーダーでは、「地図を表示」と「経路を検索」のメニューが表示され、
「地図を表示」だと該当の位置情報の周辺地図が表示され、
「経路を検索」にすると現在地から該当の位置までの経路がGoogle Mapで表示されました。

# おわりに

ロゴ画像を中心にいい具合で配置することもできれば、なおよし。