yoppa.org


多摩美 - “iTamabi” – iPhoneアプリ開発プロジェクト 2010

openFrameworks for iPhone:お絵描きアプリを作る

今日の内容

  • ドロー(お絵描き)アプリをつくる
  • 簡単なサンプルから徐々に複雑なサンプルへと進めていきます

ドロー系アプリのサンプル

  • FatTag Deluxe Katsu Edition! http://fffff.at/fattag-deluxe-katsu-edition
    • いろいろな写真を背景にタギング(落書き)することができる
    • スプレーで描いた際のインクの垂れなどをリアルに再現

ドローアプリを作る

ステップ1:タッチした軌跡に円を描く

  • touchDownとtouchMoveした際のX座標とY座標を取得
  • 取得した座標の場所に円を描いていく
  • 円の連なりで線を描いていく

testApp.h

#pragma once

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
    
public:
    void setup();
    void update();
    void draw();
    void exit();
    
    void touchDown(ofTouchEventArgs &touch);
    void touchMoved(ofTouchEventArgs &touch);
    void touchUp(ofTouchEventArgs &touch);
    void touchDoubleTap(ofTouchEventArgs &touch);
    
    void lostFocus();
    void gotFocus();
    void gotMemoryWarning();
    void deviceOrientationChanged(int newOrientation);
    
    ofPoint touchLoc; //画面をタッチしている場所
    bool drawing; //ドロー中かどうか
};

testApp.mm

#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup(){	
	//iPhone基本設定
	ofRegisterTouchEvents(this);
	ofxAccelerometer.setup();
	ofxiPhoneAlerts.addListener(this);
	
    //横位置で起動
    iPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_RIGHT);

    //画面の基本設定
    ofBackground(255, 255, 255);
    ofSetBackgroundAuto(false);
    drawing = false;
}

//--------------------------------------------------------------
void testApp::update(){

}

//--------------------------------------------------------------
void testApp::draw(){
    //もし現在ドロー中だったら
    if (drawing) {
        //色を黒に
        ofSetColor(0, 0, 0);
        //タッチしている場所に円を描く
        ofCircle(touchLoc.x, touchLoc.y, 10);
    }
}

//--------------------------------------------------------------
void testApp::exit(){

}

//--------------------------------------------------------------
void testApp::touchDown(ofTouchEventArgs &touch){
    //ドロー中に
    drawing = true;
    //タッチしている場所を記録
    touchLoc.x = touch.x;
    touchLoc.y = touch.y;
}

//--------------------------------------------------------------
void testApp::touchMoved(ofTouchEventArgs &touch){
    //タッチしている場所を記録
    touchLoc.x = touch.x;
    touchLoc.y = touch.y;
}

//--------------------------------------------------------------
void testApp::touchUp(ofTouchEventArgs &touch){
    //ドローを終了
    drawing = false;
}

//--------------------------------------------------------------
void testApp::touchDoubleTap(ofTouchEventArgs &touch){

}

//--------------------------------------------------------------
void testApp::lostFocus(){

}

//--------------------------------------------------------------
void testApp::gotFocus(){

}

//--------------------------------------------------------------
void testApp::gotMemoryWarning(){

}

//--------------------------------------------------------------
void testApp::deviceOrientationChanged(int newOrientation){

}

ステップ2:線で描く

  • ひとつ前のタッチした座標を記録しておく
  • ひとつ前のタッチした座標と現在の座標を線で結ぶ

testApp.h

#pragma once

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
    
public:
    void setup();
    void update();
    void draw();
    void exit();
    
    void touchDown(ofTouchEventArgs &touch);
    void touchMoved(ofTouchEventArgs &touch);
    void touchUp(ofTouchEventArgs &touch);
    void touchDoubleTap(ofTouchEventArgs &touch);
    
    void lostFocus();
    void gotFocus();
    void gotMemoryWarning();
    void deviceOrientationChanged(int newOrientation);

    ofPoint touchLoc; //現在タッチしている場所
    ofPoint lastTouchLoc; //ひとつ前のタッチしている場所
    bool drawing; //ドロー中かどうか
};

testApp.mm

#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup(){	
    //iPhone基本設定
	ofRegisterTouchEvents(this);
	ofxAccelerometer.setup();
	ofxiPhoneAlerts.addListener(this);
	
    //横位置で起動
    iPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_RIGHT);
    
    //画面基本設定
    ofSetBackgroundAuto(false);
    ofBackground(255, 255, 255);
    ofSetLineWidth(3);
    ofEnableSmoothing();
    
    drawing = false;
}

//--------------------------------------------------------------
void testApp::update(){

}

//--------------------------------------------------------------
void testApp::draw(){
    //もしドロー中だったら
    if (drawing) {
        ofSetColor(0, 0, 0);
        //ひとつ前のタッチした座標から現在の座標まで線を描く
        ofLine(lastTouchLoc.x, lastTouchLoc.y, touchLoc.x, touchLoc.y);
    }
}

//--------------------------------------------------------------
void testApp::exit(){

}

//--------------------------------------------------------------
void testApp::touchDown(ofTouchEventArgs &touch){
    //タッチしている場所を記録
    touchLoc.x = touch.x;
    touchLoc.y = touch.y;
}

//--------------------------------------------------------------
void testApp::touchMoved(ofTouchEventArgs &touch){
    //ドロー中に
    drawing = true;
    //ひとつ前の座標を記録
    lastTouchLoc = touchLoc;
    //タッチしている場所を記録
    touchLoc.x = touch.x;
    touchLoc.y = touch.y;
}

//--------------------------------------------------------------
void testApp::touchUp(ofTouchEventArgs &touch){
    //ドローを終了
    drawing = false;
}

//--------------------------------------------------------------
void testApp::touchDoubleTap(ofTouchEventArgs &touch){
    ofSetColor(255, 255, 255);
    ofRect(0, 0, ofGetWidth(), ofGetHeight());
}

//--------------------------------------------------------------
void testApp::lostFocus(){

}

//--------------------------------------------------------------
void testApp::gotFocus(){

}

//--------------------------------------------------------------
void testApp::gotMemoryWarning(){

}

//--------------------------------------------------------------
void testApp::deviceOrientationChanged(int newOrientation){

}

ステップ3:線の太さを変化させる

  • 前のタッチした座標から、現在の座標までの移動した距離を測定
  • 移動した距離に応じて線の太さを変化させる
    • 移動距離が長いときは細い線で
    • 移動距離が短いときには太い線で
  • 筆で書いたような効果

testApp.h

#pragma once

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
    
public:
    void setup();
    void update();
    void draw();
    void exit();
    
    void touchDown(ofTouchEventArgs &touch);
    void touchMoved(ofTouchEventArgs &touch);
    void touchUp(ofTouchEventArgs &touch);
    void touchDoubleTap(ofTouchEventArgs &touch);
    
    void lostFocus();
    void gotFocus();
    void gotMemoryWarning();
    void deviceOrientationChanged(int newOrientation);

    ofPoint touchLoc; //現在タッチしている場所
    ofPoint lastTouchLoc; //ひとつ前のタッチしている場所
    bool drawing; //ドロー中かどうか
};

testApp.mm

#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup(){	
    //iPhone基本設定
	ofRegisterTouchEvents(this);
	ofxAccelerometer.setup();
	ofxiPhoneAlerts.addListener(this);
	
    //横位置で起動
    iPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_RIGHT);
    
    //画面基本設定
    ofSetBackgroundAuto(false);
    ofBackground(255, 255, 255);
    ofEnableSmoothing();
    drawing = false;
}

//--------------------------------------------------------------
void testApp::update(){

}

//--------------------------------------------------------------
void testApp::draw(){
    //ドロー中だったら
    if (drawing) {
        //前のタッチした座標と現在の座標の距離を算出
        float dist = ofDist(lastTouchLoc.x, lastTouchLoc.y, touchLoc.x, touchLoc.y);
        //線の太さを決定
        float lineWidth = 14 - dist * 0.5;
        //4ピクセルよりは細くならないように
        if (lineWidth < 4) {
            lineWidth = 4;
        }
        ofSetColor(0, 0, 0);
        //算出した太さで円を描く
        ofCircle(touchLoc.x, touchLoc.y, lineWidth);
    }
}

//--------------------------------------------------------------
void testApp::exit(){
    
}

//--------------------------------------------------------------
void testApp::touchDown(ofTouchEventArgs &touch){
    //タッチしている場所を記録
    touchLoc.x = touch.x;
    touchLoc.y = touch.y;
}

//--------------------------------------------------------------
void testApp::touchMoved(ofTouchEventArgs &touch){
    //ドロー中に
    drawing = true;
    //ひとつ前の座標を記録
    lastTouchLoc = touchLoc;
    //タッチしている場所を記録
    touchLoc.x = touch.x;
    touchLoc.y = touch.y;
}

//--------------------------------------------------------------
void testApp::touchUp(ofTouchEventArgs &touch){
    //ドローを終了
    drawing = false;
}

//--------------------------------------------------------------
void testApp::touchDoubleTap(ofTouchEventArgs &touch){
    //ダブルタップで画面をリセット
    drawing = false;
    ofSetColor(255, 255, 255);
    ofRect(0, 0, ofGetWidth(), ofGetHeight());
}

//--------------------------------------------------------------
void testApp::lostFocus(){

}

//--------------------------------------------------------------
void testApp::gotFocus(){

}

//--------------------------------------------------------------
void testApp::gotMemoryWarning(){

}

//--------------------------------------------------------------
void testApp::deviceOrientationChanged(int newOrientation){

}

ステップ4:よりなめらかな線に

  • 線が細くなった際に、線が切れぎれになってしまう問題に対処
  • 座標を一気に移動するのではなく、補完していくことで、なめらかな線にしている

testApp.h

#pragma once

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
    
public:
    void setup();
    void update();
    void draw();
    void exit();
    
    void touchDown(ofTouchEventArgs &touch);
    void touchMoved(ofTouchEventArgs &touch);
    void touchUp(ofTouchEventArgs &touch);
    void touchDoubleTap(ofTouchEventArgs &touch);
    
    void lostFocus();
    void gotFocus();
    void gotMemoryWarning();
    void deviceOrientationChanged(int newOrientation);

    ofPoint touchLoc; //現在タッチしている場所
    ofPoint lastTouchLoc; //ひとつ前のタッチしている場所
    bool drawing; //ドロー中かどうか
    float interpolate; //補完の割合

};

testApp.mm

#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup(){	
    //iPhone基本設定
	ofRegisterTouchEvents(this);
	ofxAccelerometer.setup();
	ofxiPhoneAlerts.addListener(this);
	
    //横位置で起動
    iPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_RIGHT);
    
    //画面基本設定
    ofSetBackgroundAuto(false);
    ofBackground(255, 255, 255);
    ofEnableSmoothing();
    drawing = false;
    
    //補完の割合を設定
    interpolate = 0.2;
}

//--------------------------------------------------------------
void testApp::update(){

}

//--------------------------------------------------------------
void testApp::draw(){
    if (drawing) {
        
        //前のタッチした座標と現在の座標の距離を算出
        float dist = ofDist(lastTouchLoc.x, lastTouchLoc.y, touchLoc.x, touchLoc.y);
        //線の太さを決定
        float lineWidth = 16 - dist * 0.2;
        //6ピクセルよりは細くならないように
        if (lineWidth < 6) {
            lineWidth = 6;
        }
        ofSetColor(0, 0, 0);
        //座標を一定の割合で補完している
        lastTouchLoc += (touchLoc - lastTouchLoc) * interpolate;
        //円を描く
        ofFill();
        ofCircle(lastTouchLoc.x, lastTouchLoc.y, lineWidth);
        ofNoFill();
        ofCircle(lastTouchLoc.x, lastTouchLoc.y, lineWidth);
    }
}

//--------------------------------------------------------------
void testApp::exit(){

}

//--------------------------------------------------------------
void testApp::touchDown(ofTouchEventArgs &touch){
    //タッチしている場所を記録
    touchLoc.x = touch.x;
    touchLoc.y = touch.y;
    //ひとつ前の座標を記録
    lastTouchLoc = touchLoc;
}

//--------------------------------------------------------------
void testApp::touchMoved(ofTouchEventArgs &touch){
    //ドロー中に
    drawing = true;
    
    //タッチしている場所を記録
    touchLoc.x = touch.x;
    touchLoc.y = touch.y;
}

//--------------------------------------------------------------
void testApp::touchUp(ofTouchEventArgs &touch){
    //ドローを終了
    drawing = false;
}

//--------------------------------------------------------------
void testApp::touchDoubleTap(ofTouchEventArgs &touch){
    //ダブルタップで画面をリセット
    drawing = false;
    ofSetColor(255, 255, 255);
    ofRect(0, 0, ofGetWidth(), ofGetHeight());
}

//--------------------------------------------------------------
void testApp::lostFocus(){

}

//--------------------------------------------------------------
void testApp::gotFocus(){

}

//--------------------------------------------------------------
void testApp::gotMemoryWarning(){

}

//--------------------------------------------------------------
void testApp::deviceOrientationChanged(int newOrientation){

}

ステップ5:文字で描く

  • フォントを読みこんで、文字によって線を描く

testApp.h

#pragma once

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
    
public:
    void setup();
    void update();
    void draw();
    void exit();
    
    void touchDown(ofTouchEventArgs &touch);
    void touchMoved(ofTouchEventArgs &touch);
    void touchUp(ofTouchEventArgs &touch);
    void touchDoubleTap(ofTouchEventArgs &touch);
    
    void lostFocus();
    void gotFocus();
    void gotMemoryWarning();
    void deviceOrientationChanged(int newOrientation);
    
    ofPoint touchLoc;
    ofPoint lastTouchLoc;
    float interpolate;
    bool drawing;
    ofTrueTypeFont myFont;
    string letters;
    float size;
    int counter;
};

testApp.mm

#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup(){	
	//iPhone基本設定
	ofRegisterTouchEvents(this);
	ofxAccelerometer.setup();
	ofxiPhoneAlerts.addListener(this);
	
    //横位置で起動
    iPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_RIGHT);
    
    //画面基本設定
    ofSetBackgroundAuto(false);
    ofBackground(255, 255, 255);
    ofEnableSmoothing();
    drawing = false;
    
    //表示する文字列を設定
    letters = "hello iphone!!";
    counter = 0;
    interpolate = 0.2;
}

//--------------------------------------------------------------
void testApp::update(){
    
}

//--------------------------------------------------------------
void testApp::draw(){
    //文字で線を描く
    float dist = ofDist(lastTouchLoc.x, lastTouchLoc.y, touchLoc.x, touchLoc.y);
    float fontSize = dist;
    if (fontSize < 12) {
        fontSize = 12;
    }
    myFont.loadFont("Georgia.ttf", dist);
    ofSetColor(0, 0, 0);
    string newLetter = letters.substr(counter, 1);
    size = myFont.stringWidth(newLetter);

    
    if (dist > size) {
        float angle = atan2(touchLoc.x - lastTouchLoc.x, lastTouchLoc.y - touchLoc.y);            
        
        ofPushMatrix();
        ofTranslate(touchLoc.x, touchLoc.y);
        ofRotateZ(angle * 180.0 / PI - 90);
        myFont.drawString(newLetter, 0, 0);
        ofPopMatrix();
        
        counter++;
        if (counter > letters.size()) {
            counter = 0;
        }
        lastTouchLoc = touchLoc;
    }
    
}

//--------------------------------------------------------------
void testApp::exit(){
    
}

//--------------------------------------------------------------
void testApp::touchDown(ofTouchEventArgs &touch){
    //タッチしている場所を記録
    touchLoc.x = touch.x;
    touchLoc.y = touch.y;
    //ひとつ前の座標を記録
    lastTouchLoc = touchLoc;
}

//--------------------------------------------------------------
void testApp::touchMoved(ofTouchEventArgs &touch){
    //ドロー中に
    drawing = true;
    
    //タッチしている場所を記録
    touchLoc.x = touch.x;
    touchLoc.y = touch.y;
}

//--------------------------------------------------------------
void testApp::touchUp(ofTouchEventArgs &touch){
    //ドローを終了
    drawing = false;
}

//--------------------------------------------------------------
void testApp::touchDoubleTap(ofTouchEventArgs &touch){
    //ダブルタップで画面をリセット
    drawing = false;
    ofSetColor(255, 255, 255);
    ofRect(0, 0, ofGetWidth(), ofGetHeight());
}

//--------------------------------------------------------------
void testApp::lostFocus(){
    
}

//--------------------------------------------------------------
void testApp::gotFocus(){
    
}

//--------------------------------------------------------------
void testApp::gotMemoryWarning(){
    
}

//--------------------------------------------------------------
void testApp::deviceOrientationChanged(int newOrientation){
    
}

サンプルファイルのダウンロード

今回の授業で紹介したプログラムのソースコードは以下からダウンロードしてください。