Hatena::ブログ(Diary)

Over&Out その後 このページをアンテナに追加 RSSフィード

2018-02-12

3D写真の機能をアプリに組み込める「Fyuse SDK」の使い方

Fyuse SDKを使うと、3D写真(=Fyuse)を撮る/見る機能をアプリに組み込むことができます。


f:id:shu223:20180212093040p:image:w600


本記事ではそんなSDKの使いどころや組み込み方法について紹介してみたいと思います。 ※念のため、このFyuseおよびFyuse SDKは、僕が所属しているFyusion社のプロダクトです。


ちなみに僕はiOSエンジニアなのでiOSの実装を紹介しますが、我々のSDKはiOS, Android, Webをサポートしています。


Fyuseとは

我々の3D写真フォーマット「Fyuse」は、静止画とも動画ともポリゴンベースの3Dモデルとも違うものです。App Storeにある同名のアプリでどんな感じかお試しいただくことができます。


f:id:shu223:20180212081601g:image





例として、僕はこんな場面でFyuseを撮ってます。※はてなダイアリーの制約で、ビューアを埋め込むことができなかったので、アニメーションGIFに変換して載せています。ぜひリンクから、Fyuseビューワで見てみてください。


f:id:shu223:20180212084457g:image

(とある街の道端にあった牛の像)


普通の写真だと、片面だけとか、前からだけになりますが、Fyuseでは立体物をマルチアングルで記録できます。



Fyuseアプリのタイムラインを見ていると、

  • 人(ファッション・コスプレ)
  • 像、彫刻、フィギュア、プラモデル
  • 壮大な景色

あたりはFyuseフォーマットが非常にマッチするなぁと思います。僕は普通に記録フォーマットのいち選択肢として日常使いしてます。


SDK導入事例

モノをいろんな角度から見れる、というところからEC系とは非常に相性が良く、車業界、ファッション業界、大手総合ECサイト等ですでにご愛顧いただいてます。


公開OKを確認できた国内事例ですと、"d fashion"さんの360°アングルでのコーディネート紹介ページがあります。

f:id:shu223:20180212083657j:image:w600


また中古車販売のガリバーさんにご利用いただいております。


「車」向けのFyuseについては会社のサイトでも詳しく紹介されてるのでぜひ見てみてください。



SDKの使い方:Fyuseを「撮影する」機能を組み込む

Fyuseを撮影する機能の実装は、Fyuse用のカメラを起動する → 撮影を開始する → 保存する という流れになります。


カメラの起動

1. `FYCamera`オブジェクトを作成する

private let camera = FYCamera()

2. `prepare`して、`startPreview`する

camera.prepare()
camera.startPreview(with: previewLayer)

`startPreview`には`AVCaptureVideoPreviewLayer`オブジェクトを渡します。

これでFyuse撮影準備完了です。


撮影開始/停止

それぞれメソッドを1つ呼ぶだけです。

camera.startRecording()
camera.stopRecording()

保存

撮影したFyuseをローカルに保存するにあたって、まずは撮影の完了イベントを受けるために`FYCaptureEventListener`プロトコルへの準拠を宣言しておき、

class ViewController: UIViewController, FYCaptureEventListener

FYCameraのリスナーとして追加しておきます。

camera.add(self)

すると、撮影中・完了時・失敗時に`fyuseCamera(_:captureEvent:)`が呼ばれるようになります。

func fyuseCamera(_ camera: FYCamera, captureEvent event: FYCaptureEvent) {
    switch event.captureStatus {
    case .inProgress:
        // 撮影中
    case .completed:
        // 撮影完了
    case .failed:
        // 撮影失敗
    }
}

ここで、保存する際には内部的に様々な処理を行うため、それらを非同期で実行するクラス`FYProcessingQueue()`を使用します。

private let processingQueue = FYProcessingQueue()!
processingQueue.processEntry(path) { 
    print("Fyuse is saved at \(path)")
}

これで撮影機能は完成。


f:id:shu223:20180212083923j:image:w250


SDKの使い方:Fyuseを「見る」機能を組み込む

基本的にはたったの2ステップ

  • 1. FYFyuseViewオブジェクトを作成する
@IBOutlet private weak var fyuseView: FYFyuseView!

ちなみにIBを使用せず次のようにコードから初期化してaddSubviewしてもokです。

private let fyuseView = FYFyuseView()

  • 2. 表示したいFyuseを`FYFyuse`オブジェクトとして1に渡す

`FYFyuse`オブジェクトは、先ほど`FYProcessingQueue()`で処理・保存した際に取得したパスを渡して初期化します。

fyuseView.fyuse = FYFyuse(filePath: path)

これだけです。

先ほど撮ったFyuseがインタラクティブに閲覧できるようになります。



More

基本機能の実装方法を解説しましたが、Fyuseは「撮る」「見る」だけでなくいろいろと機能を持ってまして、たとえば以下のようなものがあります。


Visual Search

3Dベースのディープラーニングを用いた物体検索

f:id:shu223:20180212090119j:image:w250


Car Mode

車をあらゆる角度からオンデバイスで認識し、きれいに車のFyuseを取れるようにする

f:id:shu223:20180212090334p:image:w480


Tagging

Fyuse同士をタグ付けで関連付けられる。

  • 車全体のFyuseのタイヤ部分に、タイヤにフォーカスして撮ったFyuseを関連付けたり

f:id:shu223:20180212090435p:image:w480


VR, AR & MR-ready

Fyuseで撮った人物や車等をVR/AR/MR環境にエクスポート可能

f:id:shu223:20180212090529p:image:w480


f:id:shu223:20180212090713g:image

(Fyuseの人物の背景を変え、エフェクトを載せるデモ)


Infinite Smoothness

空間内におけるフレーム間を動的に補完してスムーズに表示

  • この技術のおかげでファイルサイズは〜5MBと、非常に小さく済む

f:id:shu223:20160929041931p:image:w480


お問い合わせください

Fyuse SDKをサイトからダウンロードできるようにするのはまだ準備中です。気になった方はぜひお問い合わせください。

お問合わせは日本語でも可です。


ちなみに、今月(2018年2月)はエンジニアリングのトップと、ビジネスデベロップメント/マーケティングのトップが日本に行くので、直接ミーティングできると話も早いと思います。ぜひぜひこの機会に!


2016-06-14

【watchOS 3】API Diffsから見る watchOS 3 の新機能 #WWDC2016

世間から「コレジャナイ」と見放されつつある印象のある Apple Watch / watchOS ですが、APIの面では改善して欲しかった点がちゃんと改善されて、個人的にはまた久々に触ってみたくなっています。


watch.png


以下、「watchOS 3.0 API Diffs」「What’s New in watchOS」等のプレリリースドキュメントを見て気になった新APIを列挙していきます。


WKCrownSequencer / WKCrownDelegate

ついに!やっと!デジタルクラウンの状態が取れるようになったようです。watchOS 2ではスクロール系のUI操作に使えるだけで、操作をフックしたり、値を取ったりできなかったですからね。。

WKCrownDelegate
WKCrownDelegate.crownDidBecomeIdle(_: WKCrownSequencer?)
Added WKCrownDelegate.crownDidRotate(_: WKCrownSequencer?, rotationalDelta: Double)
WKCrownSequencer
WKCrownSequencer.delegate
WKCrownSequencer.focus()
WKCrownSequencer.isIdle
WKCrownSequencer.resignFocus()
WKCrownSequencer.rotationsPerSecond

WKGestureRecognizer

Gesture Recognizer が watchOS でも使えるようになりました。

  • WKLongPressGestureRecognizer
  • WKPanGestureRecognizer
  • WKSwipeGestureRecognizer
  • WKTapGestureRecognizer.

いや、これ自体嬉しいのですが、そもそもの不満として、これまでユーザーがタップした座標を開発者が知るすべがなかったのですが、

WKGestureRecognizer.locationInObject() -> CGPoint

WKPanGestureRecognizer.translationInObject() -> CGPoint

が追加されて、タップした座標とかパンした移動先とかを取れるようになるってことがとにかく重要かと。watchOS 2 の頃はこれができないためにあきらめた機能が色々とあった気がします。今すぐに良い例が思い浮かばないのですが、たとえばタップした位置にCore Graphicsで何かを描画するとか。


バックグラウンドタスク

ついに来たという感じです。watchOS 2 のころ色々と辛かったのが、アプリが最大75秒でdeactivateされるという制約で、せっかく「ウェアラブル」、つまり「つけっぱなしにできる」のに、アプリは動かしっぱなしにはできない、という事態になってました。お客さんから「こういうことやりたい」と相談されたアイデアのうち、この制約のせいで実現できないものがなんと多かったことか。。


で、watchOS 3 では(いくつかのケースで)バックグラウンドで処理を実行できるようになりました。以下の種類があるそうです。

Background App Refresh

Use the `WKApplicationRefreshBackgroundTask` class to update your app’s state in the background. You often use a background app refresh task to drive other tasks. For example, you might use a background app refresh task to start an NSURLSession background transfer or to schedule a background snapshot refresh task.

Background Snapshot Refresh

Use the `WKSnapshotRefreshBackgroundTask` class to update your app’s user interface. You can push, pop, or present other interface controllers, and then update the content of the desired interface controller. The system automatically takes a snapshot of your user interface as soon as this task completes.

Background Watch Connectivity

When you receive background data from the paired iPhone, the system launches your app in the background, instantiates a `WKWatchConnectivityRefreshBackgroundTask` object, and passes the task object to your extension delegate’s handleBackgroundTasks: method.

Background URL Session

When a background transfer requires authorization, or when a background transfer completes (successfully or unsuccessfully), the system creates a background NSURLSession event, instantiates a `WKURLSessionRefreshBackgroundTask` object, and passes the task object to your extension delegate’s handleBackgroundTasks: method

To learn more about handling background tasks in general, see Background Refresh Tasks.


解説を端折ると語弊が生じる気がするので What's New の記述をそのまま引用しました。それぞれの解説だけざっと読むと結構色んなことができるんじゃ、という気がしてきますが、こういうのは実際に試してみないとわからないですね。


WatchConnectivityを用いた本体側(iOS)との通信は、watchOS 2では ウォッチ側はフォアグラウンドじゃないと通信できなかった のでこれは嬉しいアップデート。


Workoutのバックグラウンドサポート

Workoutもバックグランドで動作させられるようになったとのこと。対応方法は Info.plist の WKBackgroundModes キーに `workout-processing` を追加するだけ。ちなみに Xcode 8 の Capabilities からも On/Off できます。(NDA期間中につきスクショは自粛)


AVFoundation

watchOS でも AVFoudation が使えるように!


全部のクラスが移行されるわけではないのですが、オーディオファイル再生クラスである `AVAudioPlayerNode` とか、オーディオ処理諸々(各種エフェクトとか)を司る `AVAudioEngine` (詳しくはこちら)とか、音声合成の `AVSpeechSynthesizer` とか、色々入ってます。ちゃんと動くかは実際に試してみないとわかりませんが。*1


ジャイロスコープの値の取得

API的には2からあったんですが、`gyroAvailable` プロパティで必ず `false` が返ってきて実質的に非サポートでした。Apple Watch 2 とか新しいハードが出たら使えるようになるのかなと思ってましたが、wathcOS 3 で取得できるようになるようです。


ジェスチャー・姿勢認識をやろうと思ったら加速度センサだけじゃなくてジャイロの値も大抵必要になるのでこれも嬉しい更新。


あと、iOS 10 編 で書いた Significant Elevation関連と、CMPedometer関連のAPIが追加されています。


ちなみに最大100Hz周期で取れるそうです。

Device Motion, which fuses outputs from the accelerometer and gyroscope into orientation and motion dynamics updates. Apps can register to receive these updates at rates up to 100Hz.


Core Motion の歩数取得イベントはバックグランドでも取れるらしい。

On supported devices, apps can use CMPedometer APIs to register to receive live pedometer events while running in the foreground or the background.


WKInterfaceInlineMovie

動画再生インターフェース。watchOS 1 の頃、動画再生っぽいことをやるために、動画をアプリ側で動的にフレーム毎に分解して間引いてpngシーケンスアニメーションを生成(いわゆるパラパラ漫画)してウォッチ側で再生する、ということをやってましたが(たぶんみんなこうしてた)、今となっては普通に動画を再生できるようになったようです。


SceneKit / SpriteKit サポート

  • WKInterfaceSCNScene
  • WKInterfaceSKScene

詳細は後で読む。


HomeKit

HomeKitのカメラ・ドアベル(日本で言えば呼び鈴)アクセサリをサポートしたとか。それ用のサービス・キャラクタリスティックが追加されたらしい。


WKInterfaceHMCamera というインターフェースのクラスも追加されていて、これはHomeKitのIPカメラのデータにアクセスするためのインターフェースだそうです。*2


iOS 10 の新機能

量が多いので別記事としてまとめました。


【iOS 10】API Diffsから見る iOS 10 の新機能 #WWDC2016

タイムラインでは「つまんねー」「Apple終わってる」「ジョブズがいればこんなことには・・・」という落胆の声をたくさん見かけましたが、"iOS 10.0 API Diffs" や "What’s New in iOS" 等のプレリリースドキュメントを見ると今回も新しい機能が数多く追加されていて、個人的には非常にワクワクしております。


f:id:shu223:20160614084804j:image:w600


以下、気になったものを列挙していきます。


VoIP API / CallKit

BONXというVoIP通話するプロダクトを手伝っている関係で、IP通話がどんな感じでできるようになるのか(サーバーは自分で用意するのかとか)、非常に気になります。


"Speakerbox: Using CallKit to create a VoIP app" という公式サンプルが公開されているので、後でコードを見てみようと思います。


SiriKit / Intents / IntentsUI

ついにSiriのAPIが開発者に開放されました。What's New in iOS でも一番上に書かれているので、Appleとしても iOS 10 の目玉と考えているのではないでしょうか。


SiriKit というフレームワークがあるわけではなくて、Intents / Intents UI というフレームワークがその実体となるようです。以下の6つのドメインをサポートする、とのこと。

  • Audio or video calling
  • Messaging
  • Sending or receiving payments
  • Searching photos
  • Booking a ride
  • Managing workouts

これだけ見ても何がどんな感じでどこまでできるのか、全然ピンと来ないので、後ほど以下の公式サンプルを試してみます。

  • IntentHandling: Using the Intents framework to handle custom Siri request
  • UnicornChat: Extending Your Apps with SiriKit

音声認識API / Speech

SiriKitとは別に、音声認識のAPIが追加されました。リアルタイム音声にも、録音済み音声にも使えるようです。

let recognizer = SFSpeechRecognizer()
let request = SFSpeechURLRecognitionRequest(url: audioFileURL)
recognizer?.recognitionTask(with: request, resultHandler: { (result, error) in
     print (result?.bestTranscription.formattedString)
})

"SpeakToMe: Using Speech Recognition with AVAudioEngine" という公式サンプルが公開されているので、すぐに試せます。SFSpeechRecognizerの初期化時に Locale を渡せるようになってますが、日本語もいけるのでしょうか(あとでやってみます)

private let speechRecognizer = SFSpeechRecognizer(locale: Locale(localeIdentifier: "en-US"))!

(2016.6.15追記)実際に触ってみて記事を書きました!


AVFoundation

AVCapturePhotoOutput

AVFoudationで写真を撮る際に使用していた AVCaptureStillImageOutput が deprecated になり、iOS 10 では AVCapturePhotoOutput という新クラスを使うようです。すべての写真ワークフローにunifiedなパイプラインを提供する、とのことですがカメラに詳しいわけでもないので使ってみないとわからないのですが、AddedなAPIを見ていると、Live Photo 撮影機能が開発者に開放されてたりと、色々とできることが増えてそうです。

AVCapturePhotoCaptureDelegate.capture(_: AVCapturePhotoOutput, didFinishProcessingLivePhotoToMovieFileAt: URL, duration: CMTime, photoDisplay: CMTime, resolvedSettings: AVCaptureResolvedPhotoSettings, error: NSError?)
AVCapturePhotoCaptureDelegate.capture(_: AVCapturePhotoOutput, didFinishRecordingLivePhotoMovieForEventualFileAt: URL, resolvedSettings: AVCaptureResolvedPhotoSettings)
AVCapturePhotoOutput.isLivePhotoAutoTrimmingEnabled
AVCapturePhotoOutput.isLivePhotoCaptureEnabled
AVCapturePhotoOutput.isLivePhotoCaptureSupported
AVCapturePhotoOutput.isLivePhotoCaptureSuspended
AVCapturePhotoSettings.livePhotoMovieFileURL
AVCapturePhotoSettings.livePhotoMovieMetadata
AVCaptureResolvedPhotoSettings.livePhotoMovieDimensions

AVPlayerLooper

メディア(オーディオ・ビデオのことだと思われる)の特定箇所を簡単にループさせられるクラス、とのこと。


Messages

長々とキーノートで紹介されたMessagesアプリの新機能。「使ってないし。。」という声を多くタイムラインで見かけました。が、Messagesというフレームワークが追加され、開発者ができることも何やら増えたようです。


What's New によると、App ExtensionをつくってMessagesアプリと自分のアプリを連携させられるようです。つくれるApp Extensionは以下の2種類:

  • ステッカーパック(ユーザーがMessagesで使うことができる)
  • Messages内で使えるカスタムUI(iMessage appと呼ぶ)

"Ice Cream Builder: A simple Messages app extension" という公式サンプルが公開されています。あとリファレンスもパッと見ですがプログラミングガイド的にわかりやすく書かれてそうでした。


Core Image

キーノートでのPhotosの話の中で、顔だけじゃなくて「乗馬」みたいなシーンも認識するようになった、という話があって、CIDetector, CIFeatureにもそのへん追加されてるのかなと思ったら API Diffs には残念ながらそういう変更が見当たりませんでした


フィルタは以下の5つが追加されたとのこと。アップサンプリングフィルタはちょっと試してみたい。

  • CINinePartTiled
  • CINinePartStretched
  • CIHueSaturationValueGradient
  • CIEdgePreserveUpsampleFilter
  • CIClamp

僕は一眼レフユーザではないので使わなそうですが、RAW画像を扱えるようになったとのこと。

RAW image file support is now available on iOS devices that use the A8 or A9 CPU. Core Image can decode RAW images produced by several third-party cameras as well as images produced by the iSight camera of supported iOS devices (to learn more, see AVFoundation). To process RAW images, use filterWithImageData:options: or filterWithImageURL:options: to create a CIFilter object, adjust RAW processing options with the keys listed in RAW Image Options, and read the processed image from the filter’s outputImage property.


あと、`imageWithExtent:processorDescription:argumentDigest:inputFormat:outputFormat:options:roiCallback:processor:` というメソッドが何か重要そうなのですが、理解に自信がないのでWWDCのセッション見てからコメントします。

You can now insert custom processing into a Core Image filter graph by using the imageWithExtent:processorDescription:argumentDigest:inputFormat:outputFormat:options:roiCallback:processor: method. This method adds a callback block that Core Image invokes in between filters when processing an image for display or output; in the block, you can access the pixel buffers or Metal textures containing the current state of the processed image and apply your own image processing algorithms.


MapKit

Addedは以下の3つだけ。

MKAnnotationView.init(coder: NSCoder)
NSUserActivity.mapItem
MKLaunchOptionsDirectionsModeDefault

キーノートではMapアプリの新機能の話があったのに、これだけ?と一瞬思いましたが、NSUserActivity に追加された `mapItem` というプロパティが重要っぽいです。What's New の Proactive Suggestions の項に以下のように解説されています。

In iOS 10, the NSUserActivity object includes the mapItem property, which lets you provide location information that can be used in other contexts. For example, if your app displays hotel reviews, you can use the mapItem property to hold the location of the hotel the user is viewing so that when the user switches to a travel planning app, that hotel’s location is automatically available. And if you support app search, you can use the new text-based address component properties in CSSearchableItemAttributeSet, such as thoroughfare and postalCode, to fully specify locations to which the user may want to go. Note that when you use the mapItem property, the system automatically populates the contentAttributeSet property, too.

キーノートで話してた機能と関連することだとは思いますが、正直これも自分で試してみないとよくわからない。。


Core Bluetooth

ある程度枯れてるのでハード的にBluetooth 5.0がサポートされるとかがない限りはそんなに変更ないかなと思ってましたが、やはりそんなに大きな変更はありませんでした。


主な変更点としては、

CBCentralManager.state
CBPeripheralManager.state

がなくなって、代わりにこれらの状態を管理するクラス CBManager というのが追加された、という点です。Bluetoothの状態をこのクラスから取るようになった、というだけかと。

CBManager.state
CBManagerState [enum]
CBManagerState.poweredOff
CBManagerState.poweredOn
CBManagerState.resetting
CBManagerState.unauthorized
CBManagerState.unknown
CBManagerState.unsupported

また、CBCentralManager と CBPeripheralManager に引数なしの初期化メソッドが追加されました。

CBCentralManager.init()
CBPeripheralManager.init()

あと、キャラクタリスティックのValueの有効範囲を指定する Descriptor の UUID を示す定数が追加されています。

CBUUIDCharacteristicValidRangeString

(Valid Range ディスクリプタのドキュメント)


Core Motion

Significant Elevation まわりのAPIが追加されていて、(Core Locationの)Significant Location のようにバックグラウンドでバッテリーを節約しつつ大まかな高度の変化を取れるような感じになったのかなと。

CMAltimeter.isSignificantElevationAvailable() -> Bool [class]
CMAltimeter.querySignificantElevationChange(from: Date, to: Date, withHandler: CoreMotion.CMSignificantElevationSampleHandler)
CMAltimeter.startSignificantElevationUpdates(handler: CoreMotion.CMSignificantElevationSampleHandler)
CMAltimeter.stopSignificantElevationUpdates()
CMSignificantElevationSample
CMSignificantElevationSample.elevationAscended
CMSignificantElevationSample.elevationDescended
CMSignificantElevationSample.endDate
CMSignificantElevationSample.startDate
CMSignificantElevationSampleHandler

新App Extension

個人的にはExtensionは全然つくってないのですが、「開発者ができること」としては重要なので、何が追加されて何ができるようになったのか、はおさえておきたいと思っています。


iOS 10 では以下の Extension Points が追加になったようです。

  • Call Directory
  • Intents
  • Intents UI
  • Messages
  • Notification Content
  • Notification Service
  • Sticker Pack

あと、サードパーティ製のキーボードのExtensionが強化されたとのこと。


あとで見る

Proactive Suggestions

いろいろ賢くなったよ、という話っぽいが、What's New で長めに書かれていて、関連フレームワーク・クラスも多いので後で読む。


UserNotifications / UserNotificationsUI / VideoSubscriberAccount

新フレームワークだけど、名前的には必要になったときに見ればいいかな、と思うのでまた後ほど。


その他、あまり変更がなかったフレームワーク

ここに書いた以外にもたくさんありますが、API Diffsでページを開いて見たものの中で、追加APIがなかった、もしくは「ほとんど」なかったものもまとめておきます。


以下はAddedなAPIなし。

  • AudioUnit
  • CoreLocation
  • CoreAudioKit
  • ExternalAccessory
  • OpenAL
  • MultipeerConnectivity
  • HealthKitUI

以下はほとんどAddedなし、もしくは気になる変更がなかった。

  • AVKit
  • CoreAudio
  • OpenGLES

watchOS 3 の新機能

量が多いので、別記事としてまとめました。個人的には改善して欲しかったところがちゃんと改善されてて好印象です。


*1:APIとしては存在しても実際はサポートされてない、ということもたまにあります。

*2:そもそも HomeKit 対応のネットワークカメラがまだ日本では販売されてないわけですが。。(されてたらすみません)

2016-04-16

GLKView の描画内容を AVAssetWriter を用いて動画としてエクスポートする

すごい雑なメモですが、ちゃんと書くにはまだ理解が足りてなくて、かといってローカルに放置するとまた同じことを一から自分で調べそうな気がするので、とりあえずアップ。


※古いコードを流用している部分もあるため、一部ObjC、一部Swiftです。


やりたかったこと

標題の通り、「GLKView の描画内容を AVAssetWriter に渡して動画として書き出す」ということがやりたかった。リアルタイムに処理する必要があって、要件としては20fps。


AVAssetWriterInputPixelBufferAdaptor オブジェクトがあって、

@property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor *pixelBufferAdaptor;

こう生成した CVPixelBufferRef があって、

CVPixelBufferRef pixelBuffer = NULL;
CVPixelBufferPoolCreatePixelBuffer(nil, self.pixelBufferAdaptor.pixelBufferPool, &pixelBuffer);

ここに GLKView への描画内容を反映すれば、

[self.pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:timestamp];

こんな感じで AVAssetWriter で動画エクスポートできる。


・・・ということはわかっていたけど、CVPixelBuffer に GLKView の描画内容をどうやったら渡せるのかがわからなかった。


試した方法1: テクスチャキャッシュを利用して OpenGL の FBO を書き出す

ググッてこちらで見つけた方法。以下にコードがある。

`CVOpenGLESTextureCacheCreate` と `CVOpenGLESTextureCacheCreateTextureFromImage` なるメソッドを使用してテクスチャキャッシュなるものを生成して Frame Buffer Object (FBO) を云々する(処理内容を理解できていないので適切な説明ができない)。GPUImage と同様の方法らしい。


自分のプロジェクトに移植してみたがうまく動かず、上のコードと何が違うのかにらめっこしていたが、READMEをよく読むと 元コードもちゃんと動作してない と書いてあり、リファレンスコードなしであれこれ試行錯誤する時間もないので、いったん別の方法を考えることに。


試した方法2: GLKView のスナップショットを取得する

UIView のスナップショット取得はそれなりに重い印象があったのと、リアルタイム処理(20fps)の必要がありなるべくUIKitのレイヤーで処理したくなかったので当初は思いついても頭から除外していた選択肢。


ただ方法1が頓挫して、今日中に落とし所を見つけたかったので、少々フレームレートが落ちてもいいので試してみよう、ということでこっちでやることに。


下記によると、GLKView の描画内容は `renderInContext:` ではスナップショットとれないらしい。Core Graphics だからそりゃそうか。

You need to grab the view's framebuffer pixel data using OpenGL ES. You can't do it with renderInContext:.


で、同回答にリンクがあったのが下記。

GPUImage の作者 Brad Larson さんの回答。`glReadPixels()` でピクセルデータを取り出す方法と、テクスチャキャッシュを利用する方法が提示されている。後者は方法1で断念した方向なので厳しい・・・


前者の `glReadPixels()` を利用する方法は、下記に UIImage に変換するまでのコードがあった。

- (UIImage*)snapshotRenderBuffer {

    // Bind the color renderbuffer used to render the OpenGL ES view
    // If your application only creates a single color renderbuffer which is already bound at this point, 
    // this call is redundant, but it is needed if you're dealing with multiple renderbuffers.
    // Note, replace "_colorRenderbuffer" with the actual name of the renderbuffer object defined in your class.
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);

    NSInteger dataLength = backingWidth * backingHeight * 4;
    GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));

    // Read pixel data from the framebuffer
    glPixelStorei(GL_PACK_ALIGNMENT, 4);
    glReadPixels(0.0f, 0.0f, backingWidth, backingHeight, GL_RGBA, GL_UNSIGNED_BYTE, data);

    // Create a CGImage with the pixel data
    // If your OpenGL ES content is opaque, use kCGImageAlphaNoneSkipLast to ignore the alpha channel
    // otherwise, use kCGImageAlphaPremultipliedLast
    CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGImageRef iref = CGImageCreate(
                                    backingWidth, backingHeight, 8, 32, backingWidth * 4, colorspace, 
                                    kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast,
                                    ref, NULL, true, kCGRenderingIntentDefault);

    // (sayeth abd)
    // This creates a context with the device pixel dimensions -- not points. 
    // To be compatible with all devices, you're meant to keep everything as points and a scale factor;  but,
    // this gives us a scaled down image for purposes of saving.  So, keep everything in device resolution,
    // and worry about it later...
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(backingWidth, backingHeight), NO, 0.0f);
    CGContextRef cgcontext = UIGraphicsGetCurrentContext();
    CGContextSetBlendMode(cgcontext, kCGBlendModeCopy);
    CGContextDrawImage(cgcontext, CGRectMake(0.0, 0.0, backingWidth, backingHeight), iref);

    // Retrieve the UIImage from the current context
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    // Clean up
    free(data);

    return image;
}

(結局 Core Graphics を経由するのか。。)


で、最終的に以下の回答に行きつく。


あれ、`snapshot` なんてメソッドあったっけ?と思って調べてみたら、GLKView に元から用意されていた・・・!

@property (readonly, strong) UIImage *snapshot;

最終的にうまくいった方法

guard let cisnapshot = CIImage(image: snapshot) else { fatalError() }
drawImage = cisnapshot.imageByCompositingOverImage(drawImage)

glView.ciContext.render(
    drawImage,
    toCVPixelBuffer: pixelBuffer,
    bounds: drawImage.extent,
    colorSpace: CGColorSpaceCreateDeviceRGB())

上記コードの前提

  • GLKView の `snapshot` メソッドで取得した UIImage が `snapshot` に入っている
  • `drawImage` は諸々の処理を経由した CIImage オブジェクト

2016-03-20

「本気ではじめるiPhoneアプリ作り」の紹介/iOSアプリ開発の勉強法について

Yahoo!のiOS黒帯エンジニア、西さん(および SB Creative さま)よりご献本いただきました。


f:id:shu223:20160320105728j:image:w400


『本気ではじめるiPhoneアプリ作り 黒帯エンジニアがしっかり教える基本テクニック』というタイトルで、環境構築からアプリのリリースまでをカバーした実践的入門書です。まだ発売されたばかりで、Xcode 7、Swift 2 の最新開発環境に対応 しているのもポイントです。



書籍の特長

冒頭にも書きましたが、著者の西さんは ヤフーで「黒帯」として認定 されている方で、つまり現場でバリバリのエキスパートとして認められている方です。


というわけで本書は入門書でありつつ、AutoLayout、データの永続化、サウンド、アニメーション、通信処理といった 昨今のアプリ開発には不可欠な要素 を、ToDoリストアプリや商品検索アプリといった 具体的なサンプルをつくりながら学べる「実践的」な内容となっています。

(目次より一部抜粋)

  • Chapter4 アプリの基本をマスターしよう
    • 割引計算アプリで学ぶ画面レイアウトとボタン操作
  • Chapter5 永続的なデータを扱う
    • ToDoリストアプリで学ぶデータ操作
  • Chapter6 サウンドとアニメーションの処理
    • クイズアプリで学ぶサウンド・バイブレーション・グラフィックス操作
  • Chapter7 通信できるアプリを作ろう
    • 商品検索アプリで学ぶ通信の方法

ちなみに通信処理が入るアプリにはAPIを提供するサーバー側も必要ですが、そこはYahoo!のWeb APIを利用し、サーバーサイドの実装なしで実現できるように配慮されていました。

let entryUrl: String = "https://shopping.yahooapis.jp/ShoppingWebService/V1/json/itemSearch"

下記は、著者自身による紹介記事です。

アプリを作り上げていくにはさまざまな基礎知識が必要になるわけですが、本書ではiOSアプリ開発において重点的に押さえておくべき基本をまとめつつ、実際にApp Storeへリリースできるアプリを開発できるようになるまでを爆速で支援するように書いています。


また、koogawaさんのブログでも各章ごとの詳しい書評が書かれています。


おまけ:iOSアプリ開発の勉強法について

こういう話もあって、「iOSアプリ開発はどう勉強したらいいか?」「おすすめの入門書は?」みたいな質問をされることがよくあります。なかなかそういう話をブログに書く機会もないので、今回の入門書の紹介に乗じて書かせていただこうかと。


とはいえ普遍的な話はできないし、正解もないので、あくまで「僕の場合はこうでした」という話として、参考程度にお読みいただければと。


入門書購入

iOSアプリ開発を始めたとき、まず入門書を数冊買いました。はじめてのMacbookを買った帰り、有楽町のビックカメラの技術書コーナーでパラパラめくって、「話が理解できそうなもの」を選んだ記憶があります。


当時のブログに書いた記事がこちら。

この本に載ってたサンプルアプリを、

内容を理解することなく手順をなぞって実装し、

とりあえず動かしてみました。


意味を理解しないまま手順通りにXcodeでプロジェクトをつくって、3行ぐらい、意味を理解しないまま本に書かれている文字列(プログラム)を打ち込んで、本に書いてある通りにビルドのボタンを押すと・・・シミュレータで「ブラウザアプリ」が動作したのです。ブラウザってめっちゃ難しそうって思ってたので、これはちょっとした感動でした。


第一作目のアプリ

その後「ビルドして実機で動作させる」手順まで覚えた僕が次にやったのは、本の通読ではなく、本質を理解しようとすることでもなく、

  • 手元にある書籍のサンプルコードを一通りビルドして試してみる
  • そのサンプルコードからの改変でつくれそうなアプリを考えてみる

ということでした。


で、つくってストアでのリリースまで漕ぎ着けたのが、「余命電卓」というアプリです。


『iPhone SDKの教科書』という書籍に収録されていた、年齢を計算する『Age』というサンプルが元になっています。

  • ピッカーで誕生日を設定できる
  • 設定した誕生日から算出した「現在の年齢」が表示される

というサンプルで、僕のつくった余命電卓は、単にその「現在の年齢」の代わりに「余命」(78 - 現在の年齢)を表示するようにしただけでした *1


f:id:shu223:20160320231930p:image:w280:left

f:id:shu223:20160320231928p:image:w280:left



このサンプルコードの改変に必要だった知識は、

  • アイコンなどの画像リソースの入れ替え
  • ボタンを押したら設定画面に遷移するようにする
  • 年齢の代わりに「平均寿命 - 年齢」で計算した余命を表示する
  • 余命を時間・分・秒等に換算する
  • タイマーで秒数カウントダウン

これぐらいです。画面遷移やタイマー等は、同様の機能を実現している他のサンプルをあたって、該当するコードを持ってきました。


挫折しないコツ1:わからないことには目を瞑る

Xcodeプロジェクトを新規作成するだけで、わらわらとわけのわからないファイルがたくさん生成され、実際にプログラムを書くファイルである 〜.h や 〜.m (今だと 〜.swift)にも最初からわけのわからないことが色々と書かれています。


そういうのを 全部理解しようとしなかった、のが途中で挫折しなかった秘訣かなと。


Objective-Cでいうと、@interface, @implementation って何なのかとか。assign だとか nonatomic とか。


もちろん大事ではあるのですが、プログラミングを始めたばかりの人がそういうのをひとつひとつしっかり理解してから前に進もうとすると、動くものをつくれるようになるまでが遠すぎて、挫折してしまうのかなと。


挫折しないコツ2:できないことはあきらめる

余命電卓の最初のバージョンでは、日本男性の平均寿命78歳から誕生日を引く、という超シンプルなロジックだったのですが、次に僕がやろうとしたのは、「性別を選ぶと男女それぞれの平均寿命から引き算する」というものでした。


・・・が、なぜかクラッシュする・・・


当時の僕は解決方法がわからなくて、その機能をあきらめました


プログラミングにおいて、的確にトラブルシューティングを行うことは、(ものにもよりますが)しっかりした理解と経験を必要とするわりと高等な技術だと思っています。ここで挫折して二度とプログラムをやらないよりは、あきらめて別のできそうなことを探す、というのもひとつの手かと。


サンプルドリブン勉強法

そんなわけで、僕が「Age」から「余命電卓」をつくったように、最初からある程度機能があって、動くコード、つまりサンプルコードから始めて、そこに書いてある色んなわけのわからないことには目を瞑りつつ、画像を入れ替えてみるとか、計算の足し算してるところを引き算にしてみるとか、そういう わかること/できることだけでまずはやってみる、というのも、挫折せずにプログラミングの楽しさを知る=入門するためのひとつの方法かなと、僕の経験から思います。


入門書の選び方

上記の経験をふまえつつ、個人的に入門書を選ぶ際の観点としては以下をオススメしています。

  • 自分がつくりたいアプリに近いサンプルが載っている
  • 出版年月が新しい
    • 古いと、サンプルが動かない可能性がある
    • 動かないサンプルを動くようにするのは入門者には厳しいかも
  • パラパラとめくって、自分にとって説明がわかりやすそうか?をみる
    • 人によって前提知識は全然違うし、文章の好みとかもあるので

まとめ

最新の開発環境に対応した実践的入門書「本気ではじめるiPhoneアプリ作り」を紹介しつつ、僕がアプリ開発を始めたころの勉強法についても書かせていただきました。どなたかの参考になれば幸いです!


*1:今は広告とかツイート機能とか入ってますが、リリース当初はもちろんそんな機能はありません。

2015-07-02

WWDC15のチケットは外れたけどサンフランシスコに行ってきたメモ

タイトルの通り、今年はWWDCのチケットは残念ながら外れてしまったのですが、初参加した昨年を振り返ってみると

  • セッションを(英語力と理解力と集中力の点で)リアルタイムで理解できない
  • → セッション中にドキュメント等で勉強しようとする
  • → セッション会場は(椅子間の距離が)狭いし、ネットも遅い
  • 1Fのネットコーナーに入り浸る

という、日本でもできる、というかむしろ日本での方が快適にできる過ごし方をしていたわけで、良かったのは何よりもその「iOSエンジニアの祭典」的空気感なわけで、じゃあチケットなくても行く価値はあるかも、ということで航空券購入に踏み切りました。旅程は 6/6 〜 6/15 の 8泊10日。


やったこと

ヨセミテ旅行

WWDCが始まる前の土日を利用して、iOSエンジニア4人でヨセミテへ行ってきました。シリコンバレーには何度か来ているものの、なかなか観光をする機会がなかったので、今回が初。


空港で集合し、レンタカーでそのままヨセミテ方面へ。宿はヨセミテ内ではなく、そこから車で1時間ぐらいの、別荘地的なところ。


f:id:shu223:20150703093037j:image:w600

f:id:shu223:20150703093034j:image:w600


Airbnbで(Tonnyさんが)予約してくれたところなのですが、雰囲気があって超よかったです。



ヨセミテも想像以上に楽しかった。


f:id:shu223:20150607100935j:image:w600

(川遊び)


f:id:shu223:20150703042256j:image:w600

(某El Capitan)


f:id:shu223:20150703034635j:image:w400

(なぜか乗せてもらえた本場のハーレー!)


日々もくもく開発@Realm オフィス

サンフランシスコの Realm オフィスのモニタで、みんなで基調講演のストリーミングを見ました。


f:id:shu223:20150703034810j:image:w400

(広くて綺麗で快適でした)


Realmオフィスへは基調講演以降もほぼ毎日お邪魔し、もくもくドキュメント見ては手を動かして新機能を試してました。


f:id:shu223:20150703034918j:image:w400

(ランチ風景)


f:id:shu223:20150703035002j:image:w400

(念願のTシャツもいただきました。愛用してます)


StackOverflow デビュー

StackOverflow でいくつかの質問に回答し、Reputation もいくらかつき、ついに自分も vote up やコメントができるようになりました。

今まで、「なんて素晴らしい回答だ!ありがとう!」と思っても vote up する権利すらない自分の無力さを何度歯痒く思ったことか。。


StackOverflow の Reputation は GitHub 同様に世界で通用する客観的&定量的評価だし、英語の練習になるし、人の役にも立つ、ということで自分の中でずっと重要TODOだったのですが、日々の仕事を言い訳にずっと後回しにしてきたことなので、非常に達成感があります。


HomeKit 対応デバイス購入

iOS 8 で発表された HomeKit、IoT の文脈から注目はされていたものの、対応製品がない、という状況がずっと続いていました。で、最近ぽつぽつと製品が出始めている、ということでサンフランシスコの Apple Store に行ってゲットしてきました。


f:id:shu223:20150703035336j:image:w400


Lutron社の「Caséta Wireless」という製品です。229ドルもしました。とりあえず HomeKit を体験したいだけなのでもっと安いのを・・・と店内の製品パッケージをくまなく探してみましたが、残念ながらこれしか置いてないようなので購入。


後日またブログかLT等で使用&実装報告したいと思います。


WHILL HQ 訪問、野球観戦など

昨年のTechHouse内オフィスから移転したWHILLの米国本社や、とある企業のオフィス等に遊びに行きました。


f:id:shu223:20150703035436j:image:w400



あと人生初の野球観戦。


f:id:shu223:20150703035511j:image:w400


ホットドッグ&ビール&野球という経験ができてよかったです。


Apple Watchハッカソン参戦

最終日、ちょうどSFでAppleWatchハッカソンが開催されてたので参戦。


なんと、賞をいただいてしまいました!!!!


f:id:shu223:20150614131616j:image:w400


つくったのは watchOS-2-Sampler。コミュニティへの貢献を評価していただけたようです。めっちゃ嬉しかったです。


ちなみに英語プレゼンはアドリブでは絶対にグダグダになる自信があったので、いったん話したいことを全部書き出して、流れだけ頭に入れつつ反復練習して、ハック中に話しかけてくれた優しそうな現地の人に聞いてもらったりもしました。


後日談として、こういうこともありました。


やっぱ海外でハッカソンに参加するのは楽しい。


費用

気になるお値段ですが、

  • 飛行機代:¥125,140
  • 宿代:¥44,860($45 × 8 × 124.6)

で、基本費用は17万円程度。40万オーバーだった昨年と比べて、大幅にコストダウンしました。WWDCチケット代がかからなかったのと、宿を全泊TechHouseにしたのが効いたな―と。(TechHouseについては昨年の記事をご参照ください)


あとは日々の食費、ヨセミテ旅行費(レンタカー+宿。手渡しだったのでいくらか忘れた。。)、野球観戦のチケット代(確か8000円ちょい)がかかってるので、総旅行費用としては20万円ちょいかと。


LT

せっかくSFの空気に触れて高まった意識も、帰国して日々の仕事の波にさらされるとあっという間に消えてなくなるので、「人前で発表しないといけないプレッシャー」ドリブンで勉強を進めるべく、WWDC関連の勉強会にはできるだけLT枠で申し込みをするようにしました。


6/16 「potatotips #18」


ブログ:watchOS 2 新機能の細かい話5つ #potatotips - Over&Out その後


6/17 「UI Crunch #5 スマートウォッチUIデザインの今」


ブログ:UI/UX に影響の大きい watchOS 2 の新機能 3つ #uicrunch - Over&Out その後


6/19 「WWDC Afterparty Roppongi」


ブログ:【iOS9】Core Image の新機能:文字認識/追加フィルタ47種 - Over&Out その後


6/24 「WWDC2015報告共有会@ネクスト」


ブログ:【iOS9】Audio Unit Extensions 〜オーディオエフェクトのアプリ間共有〜 - Over&Out その後


所感

チケットなしWWDC、ものすごく行ってよかったです。ドキュメント見るなら日本でもできる、とはいえ日本にいたら絶対仕事を入れてしまってドキュメント見たりサンプルつくってみたりLTしたりしなかったと思うので。


あと、WWDCのチケットがあると、高いチケット代払ってるので朝ちゃんと起きてフルに会場にいないといけないという気がどうしてもしてしまってなかなかしんどいのですが、そういうプレッシャーもなく健やかな気持ちで日々を過ごせたのもよかったです。結局毎日ドキュメント見てコード書いてたわけですが、義務感があるのとないのとでは健やか度がだいぶ違うなと。


それと、Realmオフィス。Realm の中の人をはじめ、CocoaPods の中の人や RestKit の中の人など、真のスーパーハッカーな方々が集う場で、なんというかそこにいるだけで「もっと精進せねば・・・」と意識が高まりました。


そんなこんなで、来年もチケット外れてもまた行きたい所存です。現地でお世話になった皆様方、ありがとうございました!


2009 | 08 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 11 | 12 |
2017 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2018 | 02 |