iTunes Connect から発行したプロモコードをカメラで読み取る

 こんにちは、iOS エンジニアの中村です。iOS クックパッドアプリの開発では、iTunes Connect から発行したプロモコードを使いリリース前に動作確認をしています。(2013年頃から審査通過後の動作確認がこの方法で出来るようになり、リリース時の緊張感が少し軽減されました)ここまではいいのですが、このコードは文字列で発行されるためキーボードで入力しなければなりません。毎回ランダムな文字列を手入力するのは避けたいです。そこで、このコードを iTunes Card に記載されているコードのようにカメラで読み取れる画像を生成するツール icccig を作りました。この記事では、このツールの製作過程をご紹介します。

  • App Store.app > おすすめ > コードを使う の画面(カメラで読み取るという選択肢がある)

f:id:nkmrh:20170406151004p:plain:w300

フォント

 テキストエディタに Helvetica Nune でコードを書き、その周りに枠線を付けたものが認識できるか試しましたが、枠線は認識してもコードを認識することはできませんでした。そこで、コードに使われているフォントを WhatTheFont で探しましたが、これはというものは見つかりませんでした。探している過程で「光学式文字認識のための字形(英数字)」というものを知り OCR-B がとてもよく似ていたのですが、これも認識することはできませんでした。

  • OCR-B で書いたコード

f:id:nkmrh:20170406151015j:plain:w300

  • iTunes Card のコード

f:id:nkmrh:20170323132333j:plain:w300

 フォント探しは諦め、Google の画像検索で iTunes Card のコードが沢山写っている画像を見つけることができたので、この画像からフォントを作るアプローチに変更しました。しかしよく見るとこの画像には足りない文字がありました。例えば 1 2 B Z 等々。ここで iTunes Connect からコードを100個発行して出現文字を確認したところ、そこで使われていたのは以下の20文字でした。

iTunes Connect から得られたコードの文字の種類
数字 3, 4, 6, 7, 9
英字 A, E, F, H, J, K, L, M, N, P, R, T, W, X, Y

 お店で売られている iTunes Card に使われているコードは16桁なのに対して、iTunes Connect から得られるのは12桁です。出現文字が少ないのは恐らくこのためですが、今回は iTunes Connect から得られるものに対応できればいいので足りない文字は無視して進めました。Sketch で文字をスライスした画像を並べて試したところ認識できました。これでフォントは完成です。

スクリプト

 スクリプトは2つ用意しました。1つは文字列からカメラで読み取れる画像を生成するスクリプトです。これは上記で作成したフォントでコードを描き、その周りに枠線を付けた画像を生成します。枠線の太さと文字からの距離も重要な点です。枠線をうまく認識できるように微調整してこのスクリプトを完成させました。

  • icccig で生成した画像

f:id:nkmrh:20170406151023j:plain:w300

 2つ目は生成した画像を社内画像共有サーバにアップロードし、そのリンクのリストを生成するスクリプトを用意しました。issue にこのリストをコピペしておけば、開発関係者がコードを手入力せずに済みます。

最後に

 上記のフォントとコード画像を生成するスクリプトは こちら のリポジトリにありますのでよかったら試してみてください。実際の運用で試したところ、確認項目の多いディレクターやテストエンジニアに好評でした。このツールで一旦解決できましたが、一方で Xcode プラグインとして作った方が良かったかもしれませんし、他にもっといいツールや方法があるかもしれません。何かいいアイデアをお持ちの方は是非教えていただきたいです。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/