シャープハッカソン2012春に行ってきた&アプリの技術的なお話

シャープハッカソンお疲れ様でした。
呼んでいただけて嬉しかったです。
主催のシャープさん、運営のブリリアントサービスの皆さんお疲れ様でした。非常にいい経験が出来ていい刺激になりました。

ハッカソン初参加で何ができるかわからなかったけど、いい感じにアプリが完成できてよかったです。あのレベルのアプリが2日で作れるんだから、参加者みんな凄まじい技術力ですよね。

ウチのチームで作ったアプリは「プリ♡シェア」っていう複数台の端末で同時に写真をデコれるアプリ。
え、おれ? 俺はアイディア出し以外はほとんど何もしてないお( ー`дー´)キリッ
アイディア出しの段階ではみんな「パンツ!」「パンツ!」連呼してどうなるかわからなかったけど(;´Д`)
チーム名も「かわいいはパンツ」だしな!!

ぶっちゃけ、「優勝はもらったな・・・!」って思ってたんだけど、3位ですた(´・ω・`)

4チームあって得点がそれぞれ、1位37pt、2位32pt、3位31pt(2チーム)だった。

俺が担当した部分はお絵かきActivityの部分。bluetoothへ送受信するデータ構造とかもやってた。通信自体の難しいところは全部bluetooth班に投げて、俺は何もしてないお。
なのでお絵かき部分の技術的なお話を少しだけ。

まず、描画は毎度おなじみOpenGL ES+オレオレライブラリであるeglibrary(https://github.com/eaglesakura/eglibrary)で実装されてる。

2日間で実装できた機能

  1. 線の描画(色・太さ指定あり)
  2. スタンプの描画(画像指定あり)
  3. 美白設定(美白の強さ指定あり)

ちなみに「美白」エフェクトは美白と言うよりも漂白に近い。画面全体が。

お絵かき部分

描画部分の技術的な課題はの1つは、複数端末で同じ描画データを共有する関係上、可能な限り同じ見た目を目指したいということ。
基本的にはあんまり難しく考えず、onTouchイベントで入ってくるXYのタッチ座標(ピクセルベース)を、画像に対して縦横0.0〜1.0の値=UV値に変換してお互い転送することで、座標を一元的に扱うことができる。

画像のnピクセル目って座標に変換しなかったのは、UV値のほうが分解能が良くて画像の解像度・アスペクト比に左右されない上、実際のGL正規化座標系(-1.0f〜1.0f)にマッピングしやすいから(それぞれu * 2 - 1.0f、-(v * 2 - 1.0f)で変換できる)

画面上の描画座標はglViewportで指定もできるし、画像にFITするような秒が行列を作ってもいい。余白をキャンバス風に埋めたかったから、今回は後者。

タッチ座標とUV座標のマッピングはオレオレライブラリに入ってるから、それを利用。

線描画

線の描画自体はGLのライン描画を利用。ポイントスプライトでも良かったんだけど、そっちだと点と点の間をある程度補完しなければいけないからコーディング時間のコストが高かったから見送り。問題点としてライン描画は太さの最大値がGPUによって決まっていることだけど、そこらへんはあんまり気にしないようにして、24pixの太さが最大にするとかして諦めた。
ライン描画のライブラリはなかったから、ここは専用コードを書くことで対応した。

スタンプ描画

スタンプの描画はタッチ座標を中心としたポイントスプライト描画として最初に実装。
してはみたけど、ポイントスプライト描画は最大サイズが決まっていて、画像サイズが不定になるスタンプ描画には使えないと判明。
スタンプの最大解像度に制限を加えるのはツライ。
ということで仕方なく、ポリゴンを使ったスプライト描画へ実装を変更。この描画はSpriteManagerクラスとBitmapTextureImageクラスが大活躍。

美白(漂白)機能

漂白設定は単純に画面全体を加算ブレンディングでFILLしているだけだから、難しいことはない。ただし、美白設定をMAXにしても画面全体が白飛びしないように、適当な補正はかけてあげてる。
これはSpriteManagerクラスとOpenGLManagerクラスで実装。

描画部分は書いたみたいな感じで、さほど難しくないから初日のハッカソン+ホテルで2:30くらいには出来てた。美白設定だけは最後に欲張って実装してみた。

残念だったのはフォトフレームが間に合わなかったこと。
実装したいなあ。


通信部分のお話

このアプリのキモは、全員でリアルタイムに落書きできるということ。
bluetooth通信部分はデイジーチェーン接続されている以外は全くわからない。通信部分は素人なんだよ、俺。
俺に使えるようにしてもらった機能は3つ。

  1. 自分以外の全端末へ「文字列」を送信する
  2. 自分以外のどれかの端末から「文字列」を受信したことが通知される
  3. 「誰か」がbluetoothのチェーンに追加されたことが通知される

送受信されるデータは全て文字列。
送信される文字列は2種類あって、必ずどちらかを送受信する仕様にした。

  1. 制御メッセージ
  2. 描画データ

制御メッセージ

他の端末から文字列を受け取ると「それが制御メッセージか否か」を判断して、制御メッセージならそれに応じた処理、違うならデータとして扱ってJSONのパースに移る。
制御メッセージは結果的に1つだけで、「BT接続したから画像くれ」というもの。
もちろん、送信先は「自分以外の全端末」だから、制御メッセージは画像を撮影したユーザー(メイン端末)も画像を撮影していないユーザー(サブ端末たち)も全員受け取ることになる。
受け取ったら、「自分が画像を持っている場合、画像をJSONデータ化して全端末へ送信する」って処理をしてる。
もちろん、デイジーチェーン接続だから末端へ届くまでには全端末を経由するため、非常に効率は悪い。多分、参加端末が増えたら一気に転送時間が増えていく。効率化できる部分だけど、ソコまで複雑なプログラムを書く余裕はなかったため今回は華麗にスルー。
なにせ、通信でメッセージを受け取れるようになったのが終了2時間前という慌ただしさだからね(;´Д`)

描画データ

描画データは文字列でしか送受信できないから、フォーマットはJSONに決定。
クラスオブジェクト <--> 文字列の変換はJSONICに全て頑張ってもらった。そのため、それに関わるDataクラスとPenクラスのメンバは全てpublicで実装されてる。アクセサメンドイし、publicのほうがいろいろ確実だし。
全てのデータ(画像もベクタも)は同じDataクラスで管理してる。これは、パース先のクラスを判別するコストを省きたかったから。もちろん、プログラミング時間のコストね。
パースされたら専用のリスナでその通知を受け取って、DataクラスからOpenGL管理用の描画クラスに再度変換されて再描画される。
描画用のクラスは線・スタンプでそれぞれ専用に作りこんである。共通の抽象クラスだけは定義してて、描画元はforループでdrawを行うだけ。

不安だったのはテキストベースのやり取りだからパースに時間がかかることと、通信に時間がかかること。
これは実際のところ、ほとんど気にする必要がないレベルだった。最新機種ってスゴイね。
ほぼリアルタイムで全端末のお絵かきデータがアップデートされるのを見た瞬間は最高だったwwww

その他

他に技術的に気になる部分があったらTwitterあたりで聞いてくれれば答えるお。