Delphi で Let's Zxing

# Delphi でバーコード認識
Delphi でバーコード認識させる場合、色々なライブラリがあります。
ただ Android 上でバーコード認識させるとなると、多くが Zxing のラッパーとして提供されています。

…だったら、最初から Zxing 使えば良くね！？

ということで、今回は Zxing を Delphi Android アプリに組み込んで実際に QR コードをデコードする方法です。

# Zxing とは
[Zxing](https://opensource.google.com/projects/zxing) とは、Google が開発している１次元・２次元バーコード処理ライブラリで、オープンソースで提供されています(Apache 2.0 License)。
Zxing を使うとバーコードの生成や読み取りができます。
ただし、Java で記述されているので残念ながら Delphi では Android アプリからしか呼べません。

### Zxing Delphi
Zxing の Native Delphi 実装として [Zxing Delphi](https://github.com/Spelt/ZXing.Delphi) があります。
ですが…速度がすごく遅いです。すごく。実用に耐えないので、無かった物として話を進めます。

# 準備
## Zxing.Core と Zxing.JavaSE をダウンロード
それぞれ Maven Repository からダウンロードします。

* Zxing.Core  
https://mvnrepository.com/artifact/com.google.zxing/core/3.3.3

* Zxing.JavaSE  
https://mvnrepository.com/artifact/com.google.zxing/javase/3.3.3

それぞれのページの「jar (xxxKB)」と書いてあるところをクリックするとダウンロードできます。
![koko.png](https://qiita-image-store.s3.amazonaws.com/0/12977/ab844499-f815-3a5a-41ff-5bb6ed2068b1.png)

ここでは、この記事を書いている時点での最新 3.3.3 をダウンロードしたものとします。

* core-3.3.3.jar
* javase-3.3.3.jar

## JDK のインストール
後述の Java2OP が JDK を必要とするので、JDK をダウンロードしてインストールします。

* JDK 8  
https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

インストール後は環境変数の PATH に bin フォルダを追加しておきます。
もしくは Java2OP.exe を起動するときだけ

```bat
set PATH=%PATH%;C:\Program Files\Java\jdk1.8.0_201\bin
```

としても構いません。
要は Java2OP.exe が JDK にアクセス出来れば良いです。

## Java2OP を確認
Java2OP は jar ファイルや class ファイルから、それらにアクセスするための Object Pascal ソースコードを吐き出します。

Java2OP は下記のディレクトリにあります。

```
(Delphiのインストールディレクトリ)\bin\converters\java2op\Java2OP.exe
```

# ブリッジファイルの作成
Java2OP を使って jar ファイルからブリッジファイルを作ります。
(Java2OP.exeとJDKにはPATHが通っている物とします)

```bat:Zxing.Core.pas,Zxing.JavaSE.pasの作成
>cd \ダウンロード\Zxing\ ※ここはダウンロードした jar があるフォルダを指定
>java2op -jar core-3.3.3.jar -unit Zxing.Core
>java2op -jar javase-3.3.3.jar -unit Zxing.JavaSE
```

javase-3.3.3.jar ではエラーが出ますが、今回は気にしないで大丈夫です（今回は使わないクラスなどがブリッジ化できなかったというエラーです）

これで無事終了と行きたいのですが、Java2OP がインスタンスメソッドなのにクラスメソッドとして処理してしまう部分があるので、これを修正します。
今回は使用する decode だけ修正します。
Zxing の他の機能を使う時クラスメソッドではなくインスタンスメソッドが使用されている場合は同じように修正してください。

↓ここに定義されている decode メソッド３つをインスタンス側に移します

```pascal:Zxing.Core.pas(元)
  JMultiFormatReaderClass = interface(Jzxing_ReaderClass)
    ['{4673BE75-3623-4DFA-A0D6-C7953A38519D}']
    // 下記の３つ
    {class} function decode(P1: JBinaryBitmap): Jzxing_Result; cdecl; overload;
    {class} function decode(P1: JBinaryBitmap; P2: JMap): Jzxing_Result; cdecl; overload;
    {class} function decodeWithState(P1: JBinaryBitmap): Jzxing_Result; cdecl;
    {class} function init: JMultiFormatReader; cdecl;
    {class} procedure reset; cdecl;
    {class} procedure setHints(P1: JMap); cdecl;
  end;

  [JavaSignature('com/google/zxing/MultiFormatReader')]
  JMultiFormatReader = interface(Jzxing_Reader)
    ['{2E4C1DA2-C8B4-4908-BE12-8B814A523BB5}']
  end;
  TJMultiFormatReader = class(TJavaGenericImport<JMultiFormatReaderClass, JMultiFormatReader>) end;
```

```pascal:Zxing.Core.pas(修正)
  JMultiFormatReaderClass = interface(Jzxing_ReaderClass)
    ['{4673BE75-3623-4DFA-A0D6-C7953A38519D}']
    {class} function init: JMultiFormatReader; cdecl;
    {class} procedure reset; cdecl;
    {class} procedure setHints(P1: JMap); cdecl;
  end;

  [JavaSignature('com/google/zxing/MultiFormatReader')]
  JMultiFormatReader = interface(Jzxing_Reader)
    ['{2E4C1DA2-C8B4-4908-BE12-8B814A523BB5}']
    // ここに移動する
    {class} function decode(P1: JBinaryBitmap): Jzxing_Result; cdecl; overload;
    {class} function decode(P1: JBinaryBitmap; P2: JMap): Jzxing_Result; cdecl; overload;
    {class} function decodeWithState(P1: JBinaryBitmap): Jzxing_Result; cdecl;
  end;
  TJMultiFormatReader = class(TJavaGenericImport<JMultiFormatReaderClass, JMultiFormatReader>) end;
```

# 組み込み
ファイルメニューの「新規作成」→「マルチデバイスアプリケーション」を選択して、マルチデバイスアプリケーションを作成します。
そして、プロジェクトマネージャでプラットフォームを Android にします。
ここで、core.3.3.3.jar と javase-3.3.3.jar をプロジェクトマネージャにドロップします。
すると↓下図のようにライブラリに core.3.3.3.jar と javase-3.3.3.jar が追加されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/12977/3ecf7f49-641f-d0db-a8cd-aab324bdfb3c.png)

同様に Zxing.Core.pas と Zxing.JavaSE.pas も追加します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/12977/27df2af3-9739-ed96-690b-d03e41f76ad9.png)

# コーディング
実際に Zxing を使って画像からバーコードを認識するコードを書いてみました。
ちょっと長いですが、こんな感じになります。

```pascal
uses
  System.IOUtils
  , FMX.Surfaces
  , FMX.Helpers.Android
  , Androidapi.Helpers
  , Androidapi.JNIBridge
  , Androidapi.JNI.GraphicsContentViewText
  , Androidapi.JNI.JavaTypes
  , Zxing.Core
  , Zxing.Javase
  ;

// TBitmap を渡すとその中のバーコードを認識してバーコードの内容を文字列として返す
function RecognizeCode(const iSrc: TBitmap): String;
var
  Surf: TBitmapSurface;
  Image: JBitmap;
  W, H: Integer;
  Pixels: TJavaArray<Int32>;
  Source: JLuminanceSource;
  Bitmap: JBinaryBitmap;
  Binarizer: JHybridBinarizer;
  Reader: JMultiFormatReader;
  Res: Jzxing_Result;
  BarCodeText: JString;
begin
  Result := '';

  try
    Image := nil;

    // TBitmap から JBitmap へ変換します
    Surf := TBitmapSurface.Create;
    try
      Surf.Assign(iSrc);
      Image :=
        TJBitmap.JavaClass.createBitmap(
          Surf.Width,
          Surf.Height,
          TJBitmap_Config.JavaClass.ARGB_8888);

      if not SurfaceToJBitmap(Surf, Image) then
        Image := nil;
    finally
      Surf.Free;
    end;

    if Image = nil then
      Exit;

    // Bitmap を Pixel の配列として取得します
    W := Image.getWidth;
    H := Image.getHeight;

    Pixels := TJavaArray<Int32>.Create(W * H);
    try
      Image.getPixels(Pixels, 0, W, 0, 0, W, H);

      // Pixel の配列から輝度情報を生成します
      Source := TJRGBLuminanceSource.JavaClass.init(W, H, Pixels);
      if Source = nil then
        Exit;
    finally
      Pixels.DisposeOf;
    end;

    // 輝度情報を２値化します
    Binarizer := TJHybridBinarizer.JavaClass.init(Source);
    if Binarizer = nil then
      Exit;

    // ２値化 Bitmap を作成します
    Bitmap := TJBinaryBitmap.JavaClass.init(Binarizer);
    if Bitmap = nil then
      Exit;

    // バーコードリーダーを生成します（MultiFormatReader はサポートしているフォーマット全部に対応する）
    Reader := TJMultiFormatReader.JavaClass.init;
    if Reader = nil then
      Exit;

    // バーコードリーダーに２値化 Bitmap を読ませます
    Res := Reader.decode(Bitmap);
    if Res = nil then
      Exit;

    // 読み込んだ結果を受け取ります
    BarCodeText := Res.toString;
    if (BarCodeText = nil) then
      Exit;
  except
    Exit;
  end;

  // Java 文字列型を Delphi String に変換します。
  Result := JStringToString(BarCodeText);
end;
```

#実際に試す

##フォーム設計時画面
こんなフォームを作りました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/12977/5ec801cb-9144-2122-7d48-96831bbc256a.png)

一番上の画像は TImage で、QR コードをカメラで撮影して、その jpg を読み込ませた物です（リアルタイムに変わったりはしません）
こんなに画質の悪い画像でもきちんと認識できるでしょうか？

二番目には TMemo があります。
三番目には TButton があり、これを押すと次のコードが走ります。

```pascal
procedure TForm1.RecognizeClick(Sender: TObject);
begin
  Memo1.Text := RecognizeCode(Image1.Bitmap);
end;
```

つまり、Image1 の画像をデコードして文字列を Memo1 に入れます。

## Android 実機での動作

実際に実機で動かし[Recognize]ボタンを押したところです。
こんなに画質が悪いのにきちんと認識して、Memo1 に QR コードの内容が表示されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/12977/bc118ae2-1007-b1f4-b710-9b5dcaa83079.png)

# まとめ
Delphi なら Jar 化されているライブラリを組み込むのもお手の物です。
Android 限定で良いならば、ラッパーライブラリを買う必要はありません！

##ちなみに iOS は？
iOS では OS が提供している [AVMetadataMachineReadableCodeObject](https://developer.apple.com/documentation/avfoundation/avmetadatamachinereadablecodeobject?language=objc)クラスを使ってバーコードを認識できます。
これら２つを纏めてライブラリ化すると使い出があるかもしれません。