openFrameworks入門-iOS編

      openFrameworks入門-iOS編 はコメントを受け付けていません

openFrameworksのiOS用パッケージのダウンロード

ダウンロードサイト

以下の環境で検証しています。

  • macOS Catalina 10.15.5
  • iPadOS 13.5.1
  • openFrameworks 0.11.0 iOS

新規プロジェクトの作成

projectGeneratorを使って、新規プロジェクトを作成してください。

openFrameworksの場所を入力するよう要求される場合があるので、「of_v0.10.0_ios_release」のフォルダを選択します。

iOS用のプロジェクトを作るために、「Advanced options」を選択します。


 
create/updateダグを選択し、必要情報を入力します。ここでは、例としてmyImageLoaderStart180716という名称にします。Platforms:はiOS(Xcode)を選択します。
最後に「Generate」をクリックしてプロジェクトを作成します。

「open in IDE」ボタンを押すとXcodeでプロジェクトが開きますが、開かない場合もあります(理由は分かりません)。その時は、of_v0.10.0_ios_release > apps > myApps > myImageLoaderStart180716 > myImageLoaderStart180716.xcodeprojをXcodeで開いてください。

プロジェクトに開発者を登録する

iOSアプリの開発では、自身をアップルに開発者として登録する必要があります。登録が済んでいる場合には、画面左上のプロジェクトファイルをクリックして設定画面を出し、SigningのTeamで適切な開発者名を選んでください。また、Deployment InfoのDeployment Targetが古いことによってエラーが起こる場合も多々あります。基本的には接続されているiPhoneやiPadのOSのバージョンに合わせてください。

画像の表示

まずは、画像を表示するところから始めてみます。テスト用の画像を入れたdataフォルダをダウンロード・解凍して、objectフォルダとwallpaperフォルダを既存のプロジェクトフォルダ > bin > dataフォルダの中に入れてください。

image_data

背景を表示してみる

まずは、メンバ変数をofApp.hに記述します。ここでは、ofImage型の変数でwallpaperという名前にします。

ofApp.h

#pragma once

#include "ofxiOS.h"

class ofApp : public ofxiOSApp{
	
    public:
        /*省略*/
    
        ofImage wallpaper;
};

そしてofApp.mmのsetup()とdraw()に、以下の記述を加えてください。

ofApp.mm

#include "ofApp.h"

void ofApp::setup(){
	
    wallpaper.load("wallpaper/wallpaper1.png");
}

//--------------------------------------------------------------
void ofApp::draw(){

    wallpaper.draw(0,0);  //x座標、 y座標
}

そして、xCodeの左上のRunボタンを押してください。このことによって、プログラムがiPadに転送されます。
 
skitch-3
 

このコードは、wallpaperフォルダにある画像を読み込んでいます。他にもいくつか壁紙があるので、自分の好きな壁紙の名前に書き換えてみてください。

オブジェクトを表示してみる

同じ手順で、壁紙の手前にオブジェクトを表示してみます。このサンプルではオブジェクトを2つ表示してみます。

ofApp.h

#pragma once

#include "ofxiOS.h"

class ofApp : public ofxiOSApp{
	
    public:
        /*省略*/

        ofImage wallpaper;
        ofImage object1, object2;
};

ofApp.mm

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){	

    wallpaper.load("wallpaper/wallpaper1.png");
    object1.load("object/object1.png");
    object1.setAnchorPercent(0.5,0.5); //基準点を中心にする
    object2.load("object/object2.png");
    object2.setAnchorPercent(0.5,0.5); //基準点を中心にする
}

//--------------------------------------------------------------
void ofApp::draw(){

    wallpaper.draw(0,0);    //背景描画
    
    ofEnableAlphaBlending();    //PNG画像の透明部分は透過させる
    
    //draw(x, y) widthとheightを指定しなければ原寸で表示される
    object1.draw(100, 100);
    
    //draw(x, y, width, height)
    object2.draw(300, 300, object2.getWidth()*4, object2.getHeight()*4);
    
    ofDisableAlphaBlending();
}

これも同じように、好きなオブジェクトの名前に書き換えてください。

簡単なアニメーション

画像を移動してみる

次に、オブジェクトを動かしてみましょう。このサンプルではオブジェクトは1つに減らします
 

1.5

 
openFrameworksもProcessingと同じように、数学におけるy座標とは逆の方向になります。

まずは、座標用の変数を宣言してください。このサンプルでは、x、yとします。

ofApp.h

#pragma once

#include "ofxiOS.h"

class ofApp : public ofxiOSApp{
	
    public:
        /*省略*/

        ofImage wallpaper;
        ofImage object1;
        float x, y;
        float xspeed, yspeed; //x, y軸方向の速度
};

更に、ofApp.mmを変更していきます。このサンプルは、繰り返しのアニメーションですが、折り返しのアルゴリズムを掲載しているので、試してみてください。

ofApp.mm

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){	

    wallpaper.load("wallpaper/wallpaper1.png");
    object1.load("object/object1.png");
    object1.setAnchorPercent(0.5,0.5); //基準点を中心にする

    x = ofGetWidth()/2;   //オブジェクトのx座標
    y = ofGetHeight()/2;   //オブジェクトのy座標
    
    xspeed = 2; //x, y軸方向の速度
    yspeed = 3;
}

//--------------------------------------------------------------
void ofApp::update(){
    
    x += xspeed;
    y += yspeed;
    
    //繰り返し
    if(x > ofGetWidth()) x = 0;
    if(y > ofGetHeight()) y = 0;
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofBackground(255,255,255);

    wallpaper.draw(0,0);    //背景描画
    
    ofEnableAlphaBlending();    //PNG画像の透明部分は透過させる
    
    //draw(x, y) widthとheightを指定しなければ原寸で表示される
    object1.draw(x, y);
    
    ofDisableAlphaBlending();
}

タッチイベントを使ってみる

更に、画面をタッチする事によって、オブジェクトの位置を変えてみましょう。まずは、update()の中で、オブジェクト折り返すように変更します。そして、touchDown()の中を変更します。

void ofApp::update(){
    
    x += xspeed;
    y += yspeed;
    
    //折り返し
    if(x > ofGetWidth() || x < 0) xspeed = -xspeed;
    if(y > ofGetHeight() || y < 0) yspeed = -yspeed;
}

void ofApp::touchDown(ofTouchEventArgs & touch){
    
    x = touch.x;
    y = touch.y;
}

ランダムに動かしてみる

次は、タッチするごとにオブジェクトがランダムな方向に動くサンプルです。

void ofApp::touchDown(ofTouchEventArgs & touch){
    
    x = touch.x; //タッチした地点が、オブジェクトの位置になる
    y = touch.y;
    
    xspeed = ofRandom(-5, 5);   //タッチするごとに-5から5までの間の数値を自動的に設定
    yspeed = ofRandom(-5, 5);
}

加速度センサー

画像を傾けて動かしてみる

加速度センサーは意外に簡単です。まず、ライブラリのヘッダファイル「ofxiOSCoreMotion.h」を記述し、加速度センサー用の変数であるcoreMotionおよびaccelerometerData、反発力用の変数reactionを追加してください。

ofApp.h

#pragma once

#include "ofxiOS.h"
#include "ofxiOSCoreMotion.h"

class ofApp : public ofxiOSApp{
	
    public:
        /*省略*/

        ofImage wallpaper;
        ofImage object1;
        float x, y;
        float xspeed, yspeed; //x, y軸方向の速度
        float reaction;
        ofxiOSCoreMotion coreMotion;
        ofVec3f accelerometerData;
};

次にsetup()にcoreMotion.setupAccelerometer()を追加します。ひとまずxは0にしておきましょう。さらに、update()に加速度センサー用のコードを2行追加し、バウンス(折り返し)のアルゴリズムを追加します。

ofApp.mm

void ofApp::setup(){
    
    //加速度センサーを使う
    coreMotion.setupAccelerometer();
    
    wallpaper.load("wallpaper/wallpaper1.png");
    object1.load("object/object1.png");
    object1.setAnchorPercent(0.5,0.5); //基準点を中心にする
    
    x = 0;   //オブジェクトのx座標
    y = 0;   //オブジェクトのy座標
    
    xspeed = 0; //x軸方向の速度
    yspeed = 0; //y軸方向の速度
    
    reaction = 0.7; //反発力
}

//--------------------------------------------------------------
void ofApp::update(){
    
    coreMotion.update();
    accelerometerData = coreMotion.getAccelerometerData();
    
    xspeed += accelerometerData.x;    //センサーの値を速度に加算
    yspeed -= accelerometerData.y;
    
    x += xspeed;
    y += yspeed;

    //バウンス
    if(x > ofGetWidth()){
        x = ofGetWidth();
        xspeed *= -reaction;
    }else if(x < 0){
        x = 0;
        xspeed *= -reaction;
    }

    if(y > ofGetHeight()){
        y = ofGetHeight();
        yspeed *= -reaction;
    }else if(y < 0){
        y = 0;
        yspeed *= -reaction;
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofBackground(255,255,255);
    
    wallpaper.draw(0,0, ofGetWidth(), ofGetHeight());
    
    ofEnableAlphaBlending();
    object1.draw(x, y);
    ofDisableAlphaBlending();
}

setup()の中のreactionの値を変えると反発力が変わります。反発したときに減速して欲しいので、1.0未満の値を入れます。

reaction = 0.7; //反発力

完全なコードを掲載しておきます。openFrameworksのバージョンは0.10.0で作成しているので、違う開発環境で作成している場合は、プロジェクトをopenFrameworksのprojectGeneratorで作成し、「ofApp.h」と「ofApp.mm」を差し替えてください。

myImageLoader180716

カメラアプリを作ってみる

カメラアプリはかなり複雑になりますが、段階的にコーディングしていけばそんなに難しいものではありません。

iPhone, iPadのカメラ機能が使えるように設定する

新しく作ったアプリケーションの場合、iphoneやiPadのカメラ機能を使うためには「ofxiOS-Info.plist」の設定を変更する必要があります。
openFrameworks」フォルダ内の「ofxiOS-Info.plist」を選択し、下図に従っって以下の項目を追加してください。

・Privacy – Photo Library Usage Description
・Privacy – Photo Library Additions Usage Description
・Privacy – Camera Usage Description

カメラ起動、ライブラリ用のボタンを作る

まずは、カメラ起動用とライブラリを開くためのボタンを作ります。以下のファイルをダウンロードして、dataフォルダに入れてください。
button

ofApp.hは以下のように記述します。

ofApp.h

#pragma once

#include "ofxiOS.h" //iOSライブラリ用
#include "ofxiOSImagePicker.h"  //カメラ用

class ofApp : public ofxiOSApp{
	
    public:
        /*省略*/
	
    ofxiOSImagePicker camera;   //カメラ用変数
    ofImage	photo;  //撮影後の画像用変数
   
    ofImage camImg;     //ボタン用画像
    ofImage libImg;
    
    ofRectangle camRect;    //範囲指定用変数
    ofRectangle libRect;
    
    unsigned char * displayPixels;  //変換後の画像用変数
};

そして、ofApp.mmでは画像の読み込みと表示を行い、ボタンを押すとカメラ起動やライブラリの読み込みを行う機能をつけます。

ofApp.mm

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    ofBackground(170);  //グレー背景を描画
    ofSetOrientation(OF_ORIENTATION_DEFAULT);   //画面の向きは標準
    
    camImg.load("button/camera.png");   //camera, library, save画像読み込み
    libImg.load("button/library.png");
    
    //ボタンの位置を決定。rect.set(x, y, width, height)
    camRect.set(20, ofGetHeight() - camImg.getHeight() - 20, camImg.getWidth(), camImg.getHeight());
    libRect.set(100, ofGetHeight() - camImg.getHeight() - 20, camImg.getWidth(), camImg.getHeight());
}

//--------------------------------------------------------------
void ofApp::update(){
    if(camera.getImageUpdated()){
        
        unsigned char * pixels = camera.getPixels();    //カメラ画像データ
        
        int cameraW = camera.getWidth();    //撮影画像の幅、高さ
        int cameraH = camera.getHeight();
        
        displayPixels = new unsigned char [cameraW * cameraH * 4];  //変換後の画像ピクセルデータを格納する変数
        
        //1ピクセルずつ取り出して、r, g, bを抽出
        for (int y = 0; y < cameraH; y ++){
            for (int x = 0; x < cameraW; x ++){
                
                float r = pixels[y*cameraW*4 + x*4];    //赤を抽出
                float g = pixels[y*cameraW*4 + x*4 + 1];    //緑を抽出
                float b = pixels[y*cameraW*4 + x*4 + 2];    //青を抽出
               
                //変換後の値をdisplayPixelsで保存
                displayPixels[y*cameraW*4 + x*4] = MIN(MAX(r, 0), 255);   //赤
                displayPixels[y*cameraW*4 + x*4 + 1] = MIN(MAX(g, 0), 255);   //緑
                displayPixels[y*cameraW*4 + x*4 + 2] = MIN(MAX(b, 0), 255);   //青
                displayPixels[y*cameraW*4 + x*4 + 3] = 255;
            }
        }

        //変更したピクセルを変数photoにコピー
        photo.setFromPixels(displayPixels, cameraW, cameraH, OF_IMAGE_COLOR_ALPHA);
        camera.close(); //カメラ終了
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofBackground(100, 100, 100);
    
    if(photo.isAllocated()){
        ofSetColor(255, 255, 255);
        photo.draw(0, 0);   //撮影した写真を描画
    }
    
    ofSetColor(255, 255, 255, 127); //半透明にする
    camImg.draw(camRect);
    libImg.draw(libRect);
}

//--------------------------------------------------------------
void ofApp::touchUp(ofTouchEventArgs & touch){
    
    if(camRect.inside(touch.x, touch.y)){     //cameraボタンを押したら
        
        camera.setMaxDimension(MAX(1024, ofGetHeight()));   //高さは最大1024px
        camera.openCamera(1);    //カメラ起動
        
    }else if(libRect.inside(touch.x, touch.y)){ //ライブラリボタンを押したら
        
        camera.setMaxDimension(MAX(1024, ofGetHeight()));   //高さは最大1024px
        camera.openLibrary();   //library(フォトアルバム)を開く   
    }
}

完成すると、簡単なカメラアプリが完成します。

撮影画像の色を加工する

update()の中では撮影画像のピクセルを1つずつ抜き出していますが、この画像の色を変更することによって様々なエフェクトが可能になります。update()の中にある「ここに色を変換するためのコードを記述する」の箇所を書き換えてみてください。

ofApp.mm

//effect 1//////////////////////
//(色の入れ替え)
r = g;   //赤 0 ~ 255
g = b;   //緑 0 ~ 255
b = r;   //青 0 ~ 255
//effect 2//////////////////////
//(明るさ、コントラスト)
float brightness = 50;
float contrast = 2.5;

r = contrast*(r - 128) + 128 + brightness;
g = contrast*(g - 128) + 128 + brightness;
b = contrast*(b - 128) + 128 + brightness;
//effect 3//////////////////////
//(赤っぽくする)
r = r;
g = g - 100;    //緑を引く
b = b - 100;    //青を引く
//effect 4//////////////////////
//(グレー)
r = (r + g + b) / 3;
g = (r + g + b) / 3;
b = (r + g + b) / 3;
//effect 5//////////////////////
//色の反転
r = 255 - r;
g = 255 - g;
b = 255 - b;
//effect 6//////////////////////
//セピア
r = 255 - r/255*240;
g = 255 - g/255*200;
b = 255 - b/255*145;
//effect 7//////////////////////
//2色
int brightness = (r + g + b)/3;

if(brightness < 127){

    r = 32;     //赤
    g = 225;     //緑
    b = 0;     //青

}else if(brightness >=127){

    r = 255;     //赤
    g = 180;     //緑
    b = 0;     //青
}
//effect 8//////////////////////
//3色
int brightness = (r + g + b)/3;

if(brightness < 85){
        
    r = 0;     //赤
    g = 0;     //緑
    b = 0;     //青

}else if(brightness >=85 && brightness < 170){

    r = 0;     //赤
    g = 255;     //緑
    b = 0;     //青

}else if(brightness >= 170){

    r = 255;     //赤
    g = 0;     //緑
    b = 0;     //青
}

撮影画像を変形する

それでは、画像を歪ませてみましょう。現代のカメラアプリは非常に高度な変形が可能になっていますが、ここで紹介するのは基本的な収縮と膨張のエフェクトです。
まずは収縮です。Dやradというパラメータをいろいろ変えて比べてみましょう。

//effect 9//////////////////////
//収縮
float D = 150;      //収縮率。値が少なくなるほど湾曲する
float rad = 200;        //半径の大きさ

int x1 = rad*(x-cameraW/2)/sqrt(D*D+(x-cameraW/2)*(x-cameraW/2)+(y-cameraH/2)*(y-cameraH/2)) + cameraW/2;
int y1 = rad*(y-cameraH/2)/sqrt(D*D+(x-cameraW/2)*(x-cameraW/2)+(y-cameraH/2)*(y-cameraH/2)) + cameraH/2;

if (x1 >= 0 && x1 < cameraW && y1 >= 0 && y1 < cameraH) {
 
    r = pixels[y1*cameraW*4 + x1*4];
    g = pixels[y1*cameraW*4 + x1*4+1];
    b = pixels[y1*cameraW*4 + x1*4+2];
}

膨張は、収縮と比べるとかなり複雑になります。ここではupdate()をすべて表記しておきます。

//--------------------------------------------------------------
void ofApp::update(){
    if(camera.getImageUpdated()){
        
        unsigned char * pixels = camera.getPixels();    //カメラ画像データ
        
        int cameraW = camera.getWidth();    //撮影画像の幅、高さ
        int cameraH = camera.getHeight();
        
        displayPixels = new unsigned char [cameraW * cameraH * 4];  //変換後の画像ピクセルデータを格納する変数
        
        //effect 10//////////////////////
        //膨張

        float D = 250;   //膨張率。値が少なくなるほど膨張する
        float rad = 400;    //円の半径

        for (int y = 0; y < cameraH; y ++){
            for (int x = 0; x < cameraW; x ++){

                int x1 = rad*(x-cameraW/2)/sqrt(D*D+(x-cameraW/2)*(x-cameraW/2)+(y-cameraH/2)*(y-cameraH/2)) + cameraW/2;
                int y1 = rad*(y-cameraH/2)/sqrt(D*D+(x-cameraW/2)*(x-cameraW/2)+(y-cameraH/2)*(y-cameraH/2)) + cameraH/2;

                if (x1 >= 0 && x1 < cameraW && y1 >= 0 && y1 < cameraH) {
         
                    displayPixels[y1*cameraW*4 + x1*4] = pixels[y*cameraW*4 + x*4];
                    displayPixels[y1*cameraW*4 + x1*4+1] = pixels[y*cameraW*4 + x*4+1];
                    displayPixels[y1*cameraW*4 + x1*4+2] = pixels[y*cameraW*4 + x*4+2];
                    displayPixels[y1*cameraW*4 + x1*4+3] =  255;
                }
            }
        }

        for (int y = 0; y < cameraH; y ++){
            for (int x = 0; x < cameraW; x ++){

                float distance = ofDist(cameraW/2, cameraH/2, x, y);
                if (distance < rad-20) {

                int r = displayPixels[y*cameraW*4 + x*4];
                int g = displayPixels[y*cameraW*4 + x*4 + 1];
                int b = displayPixels[y*cameraW*4 + x*4 + 2];

                    if((r + g + b)/3 == 0){

                        int pixCount= 0;
                        int totalR = 0;
                        int totalG = 0;
                        int totalB = 0;

                        for(int ny = y-2; ny <= y+2; ny ++){
                            for(int nx = x-2; nx <= x+2; nx ++){

                                if(displayPixels[ny*cameraW*4 + nx*4] + displayPixels[ny*cameraW*4 + nx*4 +1] + displayPixels[ny*cameraW*4 + nx*4 + 2] != 0){

                                totalR += displayPixels[ny*cameraW*4 + nx*4];
                                totalG += displayPixels[ny*cameraW*4 + nx*4 + 1];
                                totalB += displayPixels[ny*cameraW*4 + nx*4 + 2];

                                pixCount ++;
                                }
                            }
                        }

                        displayPixels[y*cameraW*4 + x*4] = totalR/pixCount;
                        displayPixels[y*cameraW*4 + x*4 + 1] = totalG/pixCount;
                        displayPixels[y*cameraW*4 + x*4 + 2] = totalB/pixCount;
                        displayPixels[y*cameraW*4 + x*4 + 3] = 255;
                    }
                }
            }
        }
        //effect 10終わり//////////////////////

        //変更したピクセルを変数photoにコピー
        photo.setFromPixels(displayPixels, cameraW, cameraH, OF_IMAGE_COLOR_ALPHA);
        camera.close(); //カメラ終了
    }
}

画像の保存

せっかく面白い画像ができたらアルバムに保存しておきたいところです。以下のサンプルはこのカメラアプリに保存機能を追加します。

ofApp.h

#pragma once

#include "ofxiOS.h" //iOSライブラリ用
#include "ofxiOSImagePicker.h"  //カメラ用

class ofApp : public ofxiOSApp{
	
    public:
        /*省略*/

        ofImage saveImg;
        ofRectangle saveRect;
        Boolean saveFlag;   //保存ボタンを押したかどうかを判別するフラッグ
};

ofApp.mm

//--------------------------------------------------------------
void ofApp::setup(){

    /*省略*/

    saveImg.load("button/save.png");    
    saveRect.set(ofGetWidth() - libImg.getWidth() - 20, ofGetHeight() - libImg.getHeight() - 20, libImg.getWidth(), libImg.getHeight());
    
    saveFlag = false;   //saveボタン用フラッグの初期値はfalse
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofBackground(100, 100, 100);
    
    if(photo.isAllocated()){

        /*省略*/
        
        //もしsaveボタンを押したら、画面キャプチャをとって、saveFlagをfalseに戻す
        if(saveFlag){
            ofxiOSScreenGrab(NULL); //フォトアルバムにスクリーンを保存
            saveFlag = false;
        }else{
            ofSetColor(255, 255, 255, 127); //半透明にする
            saveImg.draw(saveRect); //通常はsaveボタンを表示
        }
    }
    
    //saveボタンが押されていない場合にはカメラボタンとライブラリボタン(フォトアルバム用)を表示
    if(!saveFlag){
        ofSetColor(255, 255, 255, 127); //半透明にする
        camImg.draw(camRect);
        libImg.draw(libRect);
    }
}

//--------------------------------------------------------------
void ofApp::touchUp(ofTouchEventArgs & touch){
    
    if(camRect.inside(touch.x, touch.y)){     //cameraボタンを押したら
        
        /*省略*/
        
    }else if(libRect.inside(touch.x, touch.y)){ //ライブラリボタンを押したら
        
        /*省略*/
        
    }else if(saveRect.inside(touch.x, touch.y)){    //saveボタンを押したら
        
        saveFlag = true;    //saveフラッグをtrueにする
    }
}

保存できたかどうかを確認する場合には、iOSの写真アプリを開いてみてください。

完全なコード

参考として、完全なプロジェクトファイルを掲載しておきますので、細かいコードの記述場所などが分からない場合には、このサンプルを参考にしてください。

myImagePicker180716

フリー画像素材

インターネットで検索できるフリー素材もうまく活用してください。
http://gallery.yopriceville.com/Free-Clipart-Pictures