[java][ZXing]きゅっ・きゅっきゅっ・QR▣

[link-1]: https://github.com/zxing/zxing
# 社員のモチベーションを上げる・離職率を下げるには
福利厚生を充実させる、社内イベント（飲み会・BBQ・新年会・忘年会etc）を開催する、働き方改革を推進する、合宿や禅寺研修にいかせる…なんてことをやるよりも。。。

**そうだね、お金だね!!**（エビバディパッション

個性だの多様性だので他人と違う自分カッケーな人が増えた今どき、
やりがい（昔で言う感謝www）で仲良し小好しに仕事する人なんかいないんだから、
目に見える利益を提示してあげて会社に繋ぎ止めることが1番なんじゃね？

あとSESみたいな人貸しみたいな所だったら、帰属意識を付けさせるための何かが必要なんじゃないかと。
→ほら自社よりも外勤先のほうが居心地よかったり待遇良かったら、当然そっちに行きたくなるよね!!

# ■　だけなんてつまらない

JavaでQRを生成する際、よく[【zxing】][link-1]っていうライブラリを使って、何かいい感じに出力するかと思いますが、よくQiitaとかはてブとかで紹介されてるやつって、単純にデータをファイルに出力するっていう在り来りなやつばかりかと。

いや、使い方を知りたいだけならそれで十分かなって気がしますが、それで出力されるやつって、普通なQRだったりするわけですよ（当たり前

世の中には、QRの■の部分を●にしたり、真ん中に画像を置いたり、何かいい感じのQRがいっぱい溢れているわけで、そんなQRを[【zxing】][link-1]を使って作れないかなぁということで、色々と試してみた結果をご紹介。

# 実装例を書いていくよ
出力する値については、フォーマットが決まってたりしますが、今回は出力画像の編集という点に重きを置いて書くので、そこら辺の説明は書かないので自分で調べてね！

ひとまず`QRImageUtil.java`っていうファイルに作っていくよ！

## とりあえず普通の使い方
普通の使い方が知りたくてこの記事にたどり着いた人へ向けて、普通に引数のコンテンツをQR画像に出力するようなコードも書いておくよ。

```java:普通に出力
  public static void normal(String contents) throws Exception {
    QRCodeWriter w = new QRCodeWriter();
    BitMatrix matrix = w.encode(contents, BarcodeFormat.QR_CODE, 25, 25, Map.of(
        EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.Q, // レベルはとりあえずQ
        EncodeHintType.MARGIN, 0, // 余白はなし
        EncodeHintType.CHARACTER_SET, "UTF-8")); // 文字列に日本語が来ることを考慮して文字コード設定
    BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(matrix);
    ImageIO.write(bufferedImage, "png", new File("./normalQR.png"));
  }
```

以下で出力した結果
`QRImageUtil.normal("QRキューアール");`
![normalQR.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996378/8d69cceb-1651-02c0-f85f-f221bcb79545.png)


## ■　部分を指定文字（マルチバイト）に変える
引数に【■】に変わる好きな文字を渡して出力するアレ

```java:ほら、コレが欲しかったんやろ？
  public static void otherStr(String outStr, String contents) throws Exception {
    QRCodeWriter w = new QRCodeWriter();
    BitMatrix matrix = w.encode(contents, BarcodeFormat.QR_CODE, 25, 25, Map.of(
        EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.Q,
        EncodeHintType.MARGIN, 0,
        EncodeHintType.CHARACTER_SET, "UTF-8"));

    // 上記で作成時のサイズを25*25で指定しているが、コンテンツの内容によってはソレに収まらない場合があり、
    // その場合にライブラリ内で広がったサイズを以下で取得する
    // 25のままのときもあるし、177セルとかになる場合もある
    int width = matrix.getWidth();
    int height = matrix.getHeight();
    int fontSize = 12;

    // 文字列出力するに当たりフォントを指定しないといけないので
    Font f = new Font("Meiryo", Font.PLAIN, fontSize);// 1文字ずつ出力してるので等幅フォントじゃなくてもなんでも良いよ（等幅推奨

    // 出力する画像のサイズを設定
    // matrixのwidth/heightはbit数になるため、文字サイズ分を考慮したサイズを指定
    // 後で色とか付けたいとかも有るかもしれないけど、BufferedImage.TYPE_INT_RGB を指定しておく
    BufferedImage bufferedImage = new BufferedImage(width * fontSize, height * fontSize, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = bufferedImage.createGraphics();

    // 背景を設定
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());

    // 文字のスタイルを指定
    g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VBGR);
    g.setColor(Color.BLACK);
    g.setFont(f);

    // 縦（行）のループ内で、横（カラム）のループを回して、そこの位置が黒塗りかどうかの判定を行い
    // 黒塗りだったら引数の文字を画像に出力する
    // わかりやすいようにforEachに{}を付けてる
    IntStream.range(0, height).forEach(hei -> {
      IntStream.range(0, width).forEach(wid -> {
        g.drawString(matrix.getRow(hei, null).get(wid) ? outStr : "", fontSize * wid, fontSize * (hei + 1) - 1);
      });
    });
    ImageIO.write(bufferedImage, "png", new File("./otherStrQR.png"));
  }
```
色がついているところを調べるのには`BitMatrix`の`getRow`と`get`を組み合わせて取得をする。
ちなみにコンソールに出したいだけなら以下のような実装でもOK

```java:IntStreamの所を変えるだけでいいや
    IntStream.range(0, height).forEach(hei -> {
      System.out.println(matrix.getRow(hei, null).toString().replaceAll(" ", "").replaceAll("\\.", "　").replaceAll("X", outStr));
    });
```
`getRow`を`toString`すると以下のような文字列が取得できるため`replaceAll`を以下の順でかけてあげてる
- 最初に余計な空白を削除
- 空白扱いの【.】を空白変換
- Xを出力文字に変換

```text:toStringしたときの値
 XXXXXXX. ..X.X..X XXXX..XX XXXXX
 X.....X. ..X...X. ..X.X.X. ....X
 X.XXX.X. XX..X.XX XX.XX.X. XXX.X
 X.XXX.X. .X.X..X. ...XX.X. XXX.X
 X.XXX.X. XXX..... ....X.X. XXX.X
 X.....X. X.X.XX.X X.XXX.X. ....X
 XXXXXXX. X.X.X.X. X.X.X.XX XXXXX
 ........ ..X.X.X. XXXXX... .....
 .X..X.X. XX.X..X. ..X..X.X X.X..
～　省略　～
```

以下で出力した結果
`QRImageUtil.otherStr("暇", "QRキューアール");`
サイズ（縦横幅）がちょっとデカくなっちゃうのがネックですが、ファイルサイズは12kbなのでまぁええやろ
![otherStrQR.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996378/dad40705-17a5-005b-7e5c-a5639398cba0.png)

### 追記(2025/01/30
コンソールに別の文字で出すためのもっと簡単な方法があったため連携

```java
    BitMatrix matrix = w.encode(contents, BarcodeFormat.QR_CODE, 25, 25, Map.of(
        EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.Q,
        EncodeHintType.MARGIN, 0,
        EncodeHintType.CHARACTER_SET, "UTF-8"));
    System.out.println(matrix.toString("●", "　"));
```
BitMatrix を toStringする際の引数に黒塗りの部分と空白部分の文字を渡してあげるだけでコンソールに変換した文字列が表示されます。


## QRの真ん中に画像を乗せたりしてみる
上で作ったやつを拡張して、真ん中に企業ロゴとか好きなアイコンとかなんとかかんとか乗せればいいじゃん！
```java:コレが欲しいんやろ
  public static void otherStrAndLogo(String outStr, File logo, String contents) throws Exception {
    QRCodeWriter w = new QRCodeWriter();
    BitMatrix matrix = w.encode(contents, BarcodeFormat.QR_CODE, 25, 25, Map.of(
        EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.Q,
        EncodeHintType.MARGIN, 0,
        EncodeHintType.CHARACTER_SET, "UTF-8"));

    // 上記で作成時のサイズを25*25で指定しているが、コンテンツの内容によってはソレに収まらない場合があり、
    // その場合にライブラリ内で広がったサイズを以下で取得する
    // 25のままのときもあるし、177セルとかになる場合もある
    int width = matrix.getWidth();
    int height = matrix.getHeight();
    int fontSize = 12;

    // 文字列出力するに当たりフォントを指定しないといけないので
    Font f = new Font("Meiryo", Font.PLAIN, fontSize);// 1文字ずつ出力してるので等幅フォントじゃなくてもなんでも良いよ（等幅推奨

    // 出力する画像のサイズを設定
    // matrixのwidth/heightはbit数になるため、文字サイズ分を考慮したサイズを指定
    // 後で色とか付けたいとかも有るかもしれないけど、BufferedImage.TYPE_INT_RGB を指定しておく
    BufferedImage bufferedImage = new BufferedImage(width * fontSize, height * fontSize, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = bufferedImage.createGraphics();

    // 背景を設定
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());

    // 文字のスタイルを指定
    g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VBGR);
    g.setColor(Color.BLACK);
    g.setFont(f);

    // 縦（行）のループ内で、横（カラム）のループを回して、そこの位置が黒塗りかどうかの判定を行い
    // 黒塗りだったら引数の文字を画像に出力する
    // わかりやすいようにforEachに{}を付けてる
    IntStream.range(0, height).forEach(hei -> {
      IntStream.range(0, width).forEach(wid -> {
        g.drawString(matrix.getRow(hei, null).get(wid) ? outStr : "", fontSize * wid, fontSize * (hei + 1) - 1);
      });
    });

    // ロゴ画像をぶち込む
    BufferedImage logoImg = ImageIO.read(logo);
    // QR画像の1/4サイズを取得
    // ErrorCorrectionLevel.Q
    int logoImgWidHei = bufferedImage.getWidth() / 4;
    // ロゴ配置位置(QRの真ん中から、ロゴ画像の半分分を差し引いたもの)
    int logoPosition = bufferedImage.getWidth() / 2 - logoImgWidHei / 2;

    // 透過PNGとかのロゴだと、そのまま貼り付けてしまうと■と被ってロゴが見づらくなってしまうため、
    // ロゴ画像のサイズ分の白抜きエリアを設ける
    g.setColor(java.awt.Color.WHITE);
    g.fillRect(logoPosition, logoPosition, logoImgWidHei, logoImgWidHei);

    // ロゴ画像をはっつける
    g.drawImage(logoImg, logoPosition, logoPosition, logoImgWidHei, logoImgWidHei, null);
    g.dispose();
    ImageIO.write(bufferedImage, "png", new File("./otherStrAndLogo.png"));
  }
```

ErrorCorrectionLevel.Qを指定することで、誤り訂正を25%まで上げているため、
RQの縦横サイズの4/1位をロゴ画像として定義してもギリギリイケるやろという安易な考えで実装してみた（フラグ回収
※読み取れなかったら1/6とか1/8に勝手に調整すればいいよ

以下で出力した結果
`QRImageUtil.otherStrAndLogo("鬱", new File("ダウンロードしたウチのロゴ"), "QRキューアール");`
→[ウチのロゴ](https://asil.co.jp/asil_wp/wp-content/uploads/cropped-asil_logo_favicon-32x32.png)
![otherStrAndLogo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996378/e122869b-965a-8b31-fe29-d68bac113c7f.png)

# 雑談：こんなことをやろうとした経緯
QR使って何か出来ないかなぁみたいなのをちょっと考えて見た結果
- VCard情報をQRにして名刺とかに記載していれば貰った人は登録楽じゃね？
- スマホアプリとかの設定とかをJSON化してそれをQRとかにして展開する
- 製品紹介とかのURLをQRに組み込んで企業パンフとかに掲載する

みたいなことが思いつきまして、**【誰かに見せるためのQR】** だったらそりゃ普通のQRなんかよりも見栄えとかいい感じにすれば心象よくね？という安直な考えのもと作ってみたというね。

世の中には既にブラウザで作れるサイトなどが多数ありますが、
ほら機密な情報をQRにしたいとかもあってそういったサイトで作るのには不安が有るという方も一定数居る可能性を信じて、この記事が参考になってくれればいいなぁという思いで書いてみました。