【openFrameworks】oF for iOSでretina対応する(ためにoF初期化コードを読んでみた)話【ofxiOSAppWindow】
こんばんは.1000chです.
oFを利用するメリットの一つに,モバイル端末向けのアプリを簡単に開発できる,という点があります. oF for iOSだとか,oF for Androidとかですね. ユーザ操作イベントに関するロジックとかは別個になってしまいますが,メインロジックに関しては共通コードで開発することができます. Mac上で作ったアプリがiPhone上で動くと,ちょっとした感動がありますね.
で僕もよくoF for iOSでiPadアプリ作ってたりするんですが,デフォルトでは画面解像度が1024*768で固定だったりします. これだと,せっかくiPad Airとか使っても画像がガクガクなんですよね. 今回これをretina仕様に変更する方法を知ったのでメモっておきます.
【記事書き終わって追記】 コード読みながらgdgd書いてたらとんでもない長さに... シンプルに結論だけ気になる方はこちら
普通に解像度変更しようと挑戦する
画面サイズの指定等はmain.mm
中のofSetupOpenGL
で呼ばれているはずなので,main.mm
を見ていきます.
#include "ofMain.h" #include "ofApp.h" int main(){ ofSetupOpenGL(1024,768, OF_FULLSCREEN); ofRunApp(new ofApp); }
ofSetupOpenGL
で画面サイズを指定してる感じですね.試しにsetup
内に以下を記述してみましょう.
void ofApp::setup(){ cout << ofGetWidth() << ":" << ofGetHeight() << endl; } // 出力 768:1024
うむ,それっぽいです.
ちなみにofSetOrientation(OF_ORIENTATION_90_LEFT)
叩いた後だと,この値も入れ替わります.
void ofApp::setup(){ ofSetOrientation(OF_ORIENTATION_90_LEFT): cout << ofGetWidth() << ":" << ofGetHeight() << endl; } // 出力 1024:768
じゃーここに与える数字変えれば終わりじゃん!らくしょう!
やってみます.retina解像度である2048*1536を入れてみましょう.
#include "ofMain.h" #include "ofApp.h" int main(){ ofSetupOpenGL(2048,1536, OF_FULLSCREEN); ofRunApp(new ofApp); }
さぁどうだ.
void ofApp::setup(){ ofSetOrientation(OF_ORIENTATION_90_LEFT): cout << ofGetWidth() << ":" << ofGetHeight() << endl; } // 出力 1024:768
なん...だと...???
コレ実は,Mac上のoFではフツーに動作して画面サイズ変えられます. おそらくiOSとMacで,初期化処理が異なっているのでしょうね.
retina対応しているサンプル見つけた
てなわけでしばらくretina対応はあきらめていたのですが,いろいろサンプルコードを動かしている中で「あれ..これ明らかに解像度高くね?」というものを見つけてしまったのです.
それがexample/ios/fontsExample
.
こんなもんを見つけてしまった以上,コードがどうなってるのかを見ない訳にはいきませんね.
早速main.mm
を見てみましょう.
#include "ofMain.h" #include "ofApp.h" int main(){ ofAppiOSWindow * iOSWindow = new ofAppiOSWindow(); iOSWindow->enableAntiAliasing(4); iOSWindow->enableRetina(); ofSetupOpenGL(iOSWindow, 480, 320, OF_FULLSCREEN); ofRunApp(new ofApp); }
ぼくの知ってるoF for iOSの初期化と全く違う!なんぞこれ!
ofAppiOSWindow
を読む
どーやらofAppiOSWindow
クラスが鍵を握っていそうですね.
ということでコードを読んでみると,ofAppiOSWindow
はofAppBaseWindow
のサブクラスのよう.virtual使いまくってるところを見ると,ofAppBaseWindow
が抽象クラスとして存在して,Mac,Win,iOS,android etc.向けに,それぞれサブクラスが定義されているのでは,という感じがします.
実際ofSetupOpenGL
の定義をのぞいてみると
// ProjectGeneratorで作ったときによばれてるやつ void ofSetupOpenGL(int w, int h, int screenMode){ // 利用デバイスにあわせてwindow インスタンスへのポインタを生成 #ifdef TARGET_NODISPLAY window = ofPtr<ofAppBaseWindow>(new ofAppNoWindow()); #elif defined(TARGET_OF_IOS) window = ofPtr<ofAppBaseWindow>(new ofAppiOSWindow()); #elif defined(TARGET_ANDROID) window = ofPtr<ofAppBaseWindow>(new ofAppAndroidWindow()); #elif defined(TARGET_RASPBERRY_PI) window = ofPtr<ofAppBaseWindow>(new ofAppEGLWindow()); #else window = ofPtr<ofAppBaseWindow>(new ofAppGLFWWindow()); #endif ofSetupOpenGL(window,w,h,screenMode); //windowへのポインタを下の関数に渡す } // 普段は上の関数でwindowポインタが生成され,コレがよばれる // 先のmain.mmではwindowのポインタを直接指定してコレをよんでた void ofSetupOpenGL(ofPtr<ofAppBaseWindow> windowPtr, int w, int h, int screenMode){ // 現在レンダラー情報の取得? if(!ofGetCurrentRenderer()) { #ifdef USE_PROGRAMMABLE_GL ofPtr<ofBaseRenderer> renderer(new ofGLProgrammableRenderer(false)); #else ofPtr<ofBaseRenderer> renderer(new ofGLRenderer(false)); #endif ofSetCurrentRenderer(renderer,false); } window = windowPtr; // windowはofAppRunnerのstatic変数 if(ofIsGLProgrammableRenderer()){ #if defined(TARGET_RASPBERRY_PI) static_cast<ofAppEGLWindow*>(window.get())->setGLESVersion(2); #elif defined(TARGET_LINUX_ARM) static_cast<ofAppGLFWWindow*>(window.get())->setOpenGLVersion(2,0); #elif !defined(TARGET_OPENGLES) static_cast<ofAppGLFWWindow*>(window.get())->setOpenGLVersion(3,2); #endif }else{ #if defined(TARGET_LINUX_ARM) && !defined(TARGET_RASPBERRY_PI) static_cast<ofAppGLFWWindow*>(window.get())->setOpenGLVersion(1,0); #endif } window->setupOpenGL(w, h, screenMode); // 今回の場合oFAppiOSWindowのsetupOpenGLを呼ぶ }
と呼ばれていくようで,結局のところ各サブクラスでオーバーライドしたsetupOpenGL
を
呼ぶ,というところがポイントのようです.ということでのぞいてみるも,
void ofAppiOSWindow::setupOpenGL(int w, int h, int screenMode) { windowMode = screenMode; // use this as flag for displaying status bar or not }
まさかのw
もh
も利用していないというね. そりゃー値指定してもサイズ変わらん訳ですね.
ココでは関数名の通り,OpenGLの設定が主な処理の様です.
話がすこしそれましたので,main.mm
に戻りましょう.コードを再掲します.
#include "ofMain.h" #include "ofApp.h" int main(){ ofAppiOSWindow * iOSWindow = new ofAppiOSWindow(); iOSWindow->enableAntiAliasing(4); iOSWindow->enableRetina(); ofSetupOpenGL(iOSWindow, 480, 320, OF_FULLSCREEN); ofRunApp(new ofApp); }
あきらかに怪しいのがiOSWindow->enableRetina
ですね.
改めてofAppiOSWindow
のヘッダをのぞくと,独自メソッドとして以下を持つようです.
//-------------------------------------------- ios config. bool enableHardwareOrientation(); bool disableHardwareOrientation(); bool enableOrientationAnimation(); bool disableOrientationAnimation(); bool enableRendererES2(); bool enableRendererES1(); bool isRendererES2(); bool isRendererES1(); // こいつらが今回のカギ! bool enableRetina(); bool disableRetina(); bool isRetinaEnabled(); bool isRetinaSupportedOnDevice(); bool enableDepthBuffer(); bool disableDepthBuffer(); bool isDepthBufferEnabled(); bool enableAntiAliasing(int samples); bool disableAntiAliasing(); bool isAntiAliasingEnabled(); int getAntiAliasingSampleCount();
方向変化でのアニメーション,アンチエイリアスの設定などもあるみたいですね.
enableRetina
メソッドをみてみましょう.
bool ofAppiOSWindow::enableRetina() { if(isRetinaSupportedOnDevice()) { bRetinaEnabled = true; } return bRetinaEnabled; }
retina有効フラグをtrue
にするメソッドのようで,コレを呼ぶことによりoF for iOSでも アプリをretina対応できる,ということみたい.
ただ,サンプルの様に毎度呼んでしまうのはアレなので,isRetinaSupportedOnDevice()
を利用してretian対応デバイスかのチェックはしたほうが良いかもしれないです.
こんな感じかな.
#include "ofMain.h" #include "ofApp.h" int main(){ ofAppiOSWindow * iOSWindow = new ofAppiOSWindow(); if (iOSWindow->isRetinaSupportedOnDevice()) { iOSWindow->enableRetina(); } ofSetupOpenGL(iOSWindow, 1024, 768, OF_FULLSCREEN); ofRunApp(new ofApp); }
結論
でいけそう!
#include "ofMain.h" #include "ofApp.h" int main(){ // 1.instance生成 ofAppiOSWindow * iOSWindow = new ofAppiOSWindow(); // 2. enableRetina() iOSWindow->enableRetina(); // 3. ofSetupOpenGL ofSetupOpenGL(iOSWindow, 480, 320, OF_FULLSCREEN); ofRunApp(new ofApp); }
余談:ところでretina対応のルーティングどこでやってんの?
「どこでこのフラグ使ってルーティングしてるの?」が気になったのですが,
先程見たようにofSetupOpenGL
ではretina対応のルーティングはありませんでした.
そこでbRetinaEnabled
でgrepするも,それっぽい条件分岐がみつからないという始末...
あっれー?
てことで,いわゆるiOSでのretina対応はどうするのか?をぐぐってみました.
アプリケーションを iPhone 4 の Retina Display に対応するための方法いろいろ - 24/7 twenty-four seven
OpenGL ESのRetina対応 | Objective-Audio
oFは基本OpenGL ESを利用しているので.2つめの記事が参考になるかも.曰く,
iOS4からUIView(およびEAGLViewなどのサブクラス)にcontentScaleFactorというメソッドが追加され、この値を変更することでUIViewの中の解像度を変更することができるようになりました
(中略)
glView.contentScaleFactor = [UIScreen mainScreen].scale;
基本、これだけでRetinaディスプレイのiPhone4だと倍の解像度になる
ほう. ならばcontentScaleFactor
で探してみましょう.
発見!
// EAGLViewクラス // 引数にretinaのフラグが! - (id) initWithFrame:(CGRect)frame andPreferedRenderer:(ESRendererVersion)version andDepth:(bool)depth andAA:(bool)fsaaEnabled andNumSamples:(int)samples andRetina:(bool)retinaEnabled{ if((self = [super initWithFrame:frame])) { rendererVersion = version; bUseDepth = depth; bUseFSAA = fsaaEnabled; bUseRetina = retinaEnabled; // retinaのフラグ! fsaaSamples = samples; //中略 // retinaのルーティング! if(bUseRetina){ if([[UIScreen mainScreen] respondsToSelector:@selector(scale)]){ if ([[UIScreen mainScreen] scale] > 1){ [self setContentScaleFactor:[[UIScreen mainScreen] scale]]; scaleFactor = [[UIScreen mainScreen] scale]; } else { bUseRetina = false; } } else { bUseRetina = false; } }
さらにこんなのも.
// ofxiOSEAGLViewクラス.EAGLViewのサブクラス - (id)initWithFrame:(CGRect)frame andApp:(ofxiOSApp *)appPtr { ESRendererVersion preferedRendererVersion = ESRendererVersion_11; if(ofIsGLProgrammableRenderer()) { preferedRendererVersion = ESRendererVersion_20; } self = [self initWithFrame:frame andPreferedRenderer:preferedRendererVersion andDepth:ofAppiOSWindow::getInstance()->isDepthBufferEnabled() andAA:ofAppiOSWindow::getInstance()->isAntiAliasingEnabled() andNumSamples:ofAppiOSWindow::getInstance()->getAntiAliasingSampleCount() andRetina:ofAppiOSWindow::getInstance()->isRetinaEnabled()]; // 略
bRetinaEnabled
でgrepしても見つからないわけですね.クラスメソッドのgetInstance
でインスタンス呼んで,getterのisRetinaEnabled
呼んでいたとは..
そしてofxiOSEAGLView
自体はofxiOSViewController
で生成されていました.
// ofxiOSViewControler - (id)initWithFrame:(CGRect)frame app:(ofxiOSApp *)app { currentInterfaceOrientation = pendingInterfaceOrientation = UIInterfaceOrientationPortrait; if((self = [super init])) { currentInterfaceOrientation = pendingInterfaceOrientation = self.interfaceOrientation; bReadyToRotate = NO; bFirstUpdate = NO; // ofxiOSEAGLViewの生成! self.glView = [[[ofxiOSEAGLView alloc] initWithFrame:frame andApp:app] autorelease]; self.glView.delegate = self; } return self; }
objective-cでのiOSアプリ開発あまりやったこと無いので僕のコードリーディングはここで力つきました..orz
ofxiOSAppDelegete
でこのViewControllerが指定されてたり,ofRunApp
の先でUIApplicationMain
にofxiOSAppDelegate
を呼べ!的な処理があるので,おそらくは上手いことこのviewが生成されるところまでつながっているんでしょう←
obj-cのios開発を勉強してからまた出直そうと思いますw