QRコードリーダーを作る前に、ジェネレーターを作りたい
今日びQRコードなんてそこら中にあるわけだけど、せっかくなら自分で作ったQRコードを読んでみたい。なのでまず入力した文字列をQRコードに変換するようなアプリを作りたい。Howtoではないです。やった後に見つけたんだけど、こっち

のがいいと思いました。くそぅ。
QRコードの仕様を見て実装する
のは無理、死ぬ。なので出来合いのもの、ライブラリを探す。最初からやりたい人は、こういう
のを見ながら作っていけばよいみたいです。すごいよなぁ、こういう人たち。
QR-Code-generatorライブラリを使いたい
使いたい。Githubを検索してたら、
こちらを見つけた。Javaってなってるし、いけんだろと思った。この時は。
Gradleに追記する
implementation 'io.nayuki:qrcodegen:1.4.0'
mavencentralでjarが公開されてたので、こう書いてSyncしたら自動でやってくれた。mavenでの書き方が分からんかったけど、versionクリックして右の方にgradleでの書き方が載ってて助かった。
参考

サンプルコードを書いてみる→失敗
あっるぇー?いつもどおりAlt+Enterを押しまくったのだけど、赤いのが消えてくれない。なんでやろーと思ったら、AndroidではBufferedImageは使えないみたい。なんということでしょう。あれ?ていうことはこのライブラリ利用できないの?じゃあどうしてるの?と、ちょっこし調べてみると、先駆者はBufferedImageを書き換えてBitmapというものを利用しているようだ。似たようなもんだろうし、俺にもできんだろーと思ったのが沼の始まりであった。
参考



QR-Code-generatorライブラリをAndroid用に書き換えたい
BufferedImageで受けてるんだから、ライブラリの中にBufferedImageを使ってる部分があるんだろーなーと思い、中を見てみることに。
QR-Code-generatorの中を見る
左上をProjectにして、ExternalLibraries→Gradle:io.nayuki:qrcodegen:[email protected]→qrcodegen-1.4.0.jar→io.nayuki.qrcodegenと進んでいき、中に入ってるclassファイルを開いてみると…、赤いとこあった!ここをAndroidのBitmapにしてけばいいんだろーなーと、書き換えようと思ったら
File is read-only
File is not Writable
の表示ががが…。あ、まじでこれできないやつだ、と思ったのだけれど、ソース公開されてんのに書き換えできないってどういうことだよと思ったら、自分で生のjavaファイルからjarを作成すればいけるっぽい。なんかどんどんやること増えてないか。ぐるぐる調べているうちに、このライブラリを速くしたぜ!ってなライブラリを見つけた。それもJavaオンリーやで。
どうせなので、こっちを書き換えてみたい。みたいって、できるのかな。
参考
java.awt.BufferedImageをandroid.graphics.Bitmapに置き換えたい
BufferedImageの方が何をやっているのかを見てきながら、Bitmapにそれっぽいのがないか探していく。
public BufferedImage toImage(int scale, int border) { if (scale <= 0 || border < 0) throw new IllegalArgumentException("Value out of range"); if (border > Integer.MAX_VALUE / 2 || size + border * 2L > Integer.MAX_VALUE / scale) throw new IllegalArgumentException("Scale or border too large"); BufferedImage result = new BufferedImage((size + border * 2) * scale, (size + border * 2) * scale, BufferedImage.TYPE_INT_RGB); for (int y = 0; y < result.getHeight(); y++) { for (int x = 0; x < result.getWidth(); x++) { boolean color = getModule(x / scale - border, y / scale - border); result.setRGB(x, y, color ? 0x000000 : 0xFFFFFF); } } return result; }
1行目
BufferedImageは、まぁ赤い。戻り値をBitmapにしたいから、ここはBitmapにする。
2-5行目
scaleとborderってなんだろうと思ったら、scaleは各セル(黒い点・白い点)を何個のピクセルで構成するかみたい。例えばscale=1だと、1セル=1ピクセルだし、scale=2だと、1セル=4ピクセルになる。じゃあborderはというと、外側の余白部分をどれくらい取るか、という値みたい。で、この行ではそれぞれについて、オーバーだったり足りなかったりしたときにエラーになるようにしてるようだ。ここは変えなくてもよさそう。多分。
7行目
BufferedImageのコンストラクタを読んでみる。引数が3つだから多分これだろう。縦横とカラースペースを指定してるのかな。Androidの方でいうと、BitmapFactoryかな?使ったことあるし。て、思ったんだけど違うみたい。これはリソースから読み込む用で、無から作り出すにはcreateBitmapというメソッドがあるようだ。引数3つでそれっぽいやつといったらこれかなぁと思ったので、書いてみる。
Bitmap result = Bitmap.createBitmap((size + border * 2) * scale, (size + border * 2) * scale, Bitmap.Config.ARGB_8888);
x,yはそのままで、問題はBitmap.Configなのだけど、元のTYPE_INT_RGBはアルファ無しの8ビットRGBのようでBitmap.Configにはそんなものは無い。アルファ付きで近いやつがARGB_8888なので、これでいってみよう。アルファっつーのは透明度のことらしい。
8-9行目
getHeight,getWidthはBitmapの方にもあった(getHeight,getWidth)。戻り値もintだし大丈夫だと思う。この行で全ピクセルを走査してるんだろうなぁ。多分。
11行目
setRGBは引数3つだから多分これだ。x,y座標のピクセルに第3引数のRGB値の色を設定しているんだろう。この書き方がよく分かんなくてググりまくった。3項演算子というらしい。Bitmapの方に無いぞ…、あった。setPixelって名前なのね。書いてみる。
result.setPixel(x, y, color ? Color.argb(255, 0, 0, 0) : Color.argb(255, 255, 255, 255));
Bitmapの方に
The color must be a non-premultiplied ARGB value in the sRGB color space.
とあり、BufferedImageの方は6桁16進数なのだけど、アルファをプラスして8桁16進数にしなきゃみたい。ただ書き方分かんねぇなと思ってたらColorにこう書け、という記述があったので、こうしてみた。0x表記のほうが速いのかな。まぁそうだよな、いちいち計算するより結果が書いてあったほうが早そうだもんな。いやでもそこらへんコンパイラが最適化とかしてくんないかな。してくれることを祈ろう。
書き換え後
import android.graphics.Bitmap; import android.graphics.Color;
public Bitmap toImage(int scale, int border) { if (scale <= 0 || border < 0) throw new IllegalArgumentException("Value out of range"); if (border > Integer.MAX_VALUE / 2 || size + border * 2L > Integer.MAX_VALUE / scale) throw new IllegalArgumentException("Scale or border too large"); Bitmap result = Bitmap.createBitmap((size + border * 2) * scale, (size + border * 2) * scale, Bitmap.Config.ARGB_8888); for (int y = 0; y < result.getHeight(); y++) { for (int x = 0; x < result.getWidth(); x++) { boolean color = getModule(x / scale - border, y / scale - border); result.setPixel(x, y, color ? Color.argb(255, 0, 0, 0) : Color.argb(255, 255, 255, 255)); } } return result; }
不思議な点
これでなんでQRコードの画像の白黒ができるんだろ?と疑問が湧いてきて、10行目のgetModuleが怪しいと感じて見に行ったら
public boolean getModule(int x, int y) { return 0 <= x && x < size && 0 <= y && y < size && modules[y][x]; }
こんな感じだった。多分最後のmodules[y][x]に白黒どっちにするかのデータが入ってるのかな…。とにかく一応書き換えられたので、これでいってみよう。jarを作るのです。
書き換えたjavaファイルを元にjarファイルを作りたい→無理
書き換え終わったのでjarファイルを作りたい。流れとしてはjavaファイルをclassファイルにして、それをjarファイルにまとめるって感じみたい。コマンドプロンプトでやればいいみたい。意気揚々と
javac -encoding UTF-8 *.java
とやってみると、
QrCode.java:26: エラー: パッケージandroid.graphicsは存在しません
import android.graphics.Bitmap;
^
QrCode.java:27: エラー: パッケージandroid.graphicsは存在しません
import android.graphics.Color;
^
QrCode.java:294: エラー: シンボルを見つけられません
public Bitmap toImage(int scale, int border) {
^
シンボル: クラス Bitmap
場所: クラス QrCode
QrCode.java:300: エラー: シンボルを見つけられません
Bitmap result = Bitmap.createBitmap((size + border * 2)
* scale, (size + border * 2) * scale, Bitmap.Config.ARGB_8888);
^
シンボル: クラス Bitmap
場所: クラス QrCode
QrCode.java:300: エラー: パッケージBitmapは存在しません
Bitmap result = Bitmap.createBitmap((size + border * 2)
* scale, (size + border * 2) * scale, Bitmap.Config.ARGB_8888);^
QrCode.java:300: エラー: シンボルを見つけられません
Bitmap result = Bitmap.createBitmap((size + border * 2)
* scale, (size + border * 2) * scale, Bitmap.Config.ARGB_8888);
^
シンボル: 変数 Bitmap
場所: クラス QrCode
QrCode.java:304: エラー: シンボルを見つけられません
result.setPixel(x, y, color ? Color.argb(255, 0,
0, 0) : Color.argb(255, 255, 255, 255));
^
シンボル: 変数 Color
場所: クラス QrCode
QrCode.java:304: エラー: シンボルを見つけられません
result.setPixel(x, y, color ? Color.argb(255, 0,
0, 0) : Color.argb(255, 255, 255, 255));^
シンボル: 変数 Color
場所: クラス QrCode
エラー8個
なん…だと…。どうもandroid.graphics.Bitmapとandroid.graphics.Colorが存在してないからコンパイルできないみたい。classファイルができないまま、
jar -cvf fastqrcodegen.jar *.java
implementation files('libs/fastqrcodegen.jar')
とかやっても ちゃんとできない。おいマジか、ここまできて無理なのかーいとがっかりしたところ、Android用のライブラリとして書けばいいみたいな感じの記事を発見した。ただ全部ソースをコピペだ。
参考



書き換えたjavaファイルを元にAndroidライブラリを作りたい
ここを参考に新しくModuleを作り、そこのjavaフォルダ内に元のjavaファイルと同じ名前のJavaClassを作り内容をコピペしていく。果たしてそんなことが許されるのか、ライセンス的にどうなんだという思いを置き去りに、作業を進めていった。
こうなりました。QrCodeGeneratorDemo.javaとpackage-info.javaは無くても大丈夫そうだったので、コピペしなかった。あとQrSegmentAdvanced.javaも、Call requires API level…が出てしまい、赤字の消し方が分からんかったのでコピペしなかった。最後にGradleに
implementation project(path: ':fastqrcodegen')
と追記してSyncすると使えるようになった。ただ、なんか、罪悪感があるんだけど、それはもうしょうがないよね。他のやり方がわからんし…。
Fast-QR-Code-generatorを試してみる
適当にImageViewを設置し、onCreateに
imageView = findViewById(R.id.imageView); QrCode qr0 = QrCode.encodeText("Hello, world!", QrCode.Ecc.MEDIUM); Bitmap img = qr0.toImage(4, 10); imageView.setImageBitmap(img);
結果がこちら
で、できた…、できたぞー!!感慨もひとしおである。ただ、アライメントがなーいのね。あら、そうなの。そうなのね。そっかー。ついてた。早とちり。データ量多くなると付くっぽい。ごめんなさい。
参考
ZXing Android Embeddedを使ってみる
いやー、これ見つけた時はショックだったね。今までの俺の苦労はなんだったんだ、つって。
implementation 'com.journeyapps:zxing-android-embedded:3.6.0'
をgradleに追記。
<application android:hardwareAccelerated="true">
をAndroidManifest.xmlに追記。生成の場合はいらんようだ。
try { imageView = findViewById(R.id.imageView); BarcodeEncoder barcodeEncoder = new BarcodeEncoder(); Bitmap bitmap = barcodeEncoder.encodeBitmap("Hello, world!", BarcodeFormat.QR_CODE, 400, 400); imageView.setImageBitmap(bitmap); } catch (WriterException e) { e.printStackTrace(); }
Githubのサンプルほぼまんま。
はいっ、できましたー。アライメントもついてまーす。上のにも付いてた。
所感
もっとちゃんとググる。
さぁ次はリーダーを作るぞー!
コメント