Hatena::Groupiphone-dev

hirogramの日記 このページをアンテナに追加 RSSフィード

2010-08-18手書きアプリのためのテストプログラム

実行した画面

手描きのアプリは、指の動きに追従してなめらかな線を書くのがなかなかに難しいものです。いろんなアプリが色々工夫していることかと思います。SpeedTextも工夫しています。んで、色々工夫していると、速くなったんだか遅くなったんだか自分ではよく分からなくなってきます。人間の感覚って難しい。

というわけで、手書き系アプリのための描画速度を測るかんたんなテスト用ビューを作ってみました。アプリの処理を変える前後でテストすると、速くなったかどうか分かりやすいかな、と。

実行すると、画像みたいな感じで「2秒間かけて丸を等速で12個描くような」指の動きをシュミレートします。1つの丸に60個の座標、計720個の座標を用意して、1つの座標の描画が終わるたびに、スタートからの経過時間に応じた座標を描画用のビューに渡します。(描画用のビューとは、あなたのアプリのビューです。なお、iPhoneSDKでは、本物のtouchなんとかメソッドを呼べないため、アプリ側に擬似的なtouchなんとかメソッドを用意する必要があります)

んで、touchなんとかを呼んだ回数をカウントして最後に表示します。呼べた回数が多いほどいいです。これは大雑把なスコアといえます。丸の形も見ましょう。touchに対する処理速度が偏っていると、丸が歪な形になります。あと、丸の上部がどれだけ閉じているかは、指のつけ離しに対する追従性を表します。一つの丸から次の丸に移るときにtouchEnd、touchStartを呼びますので、そこで時間がかかると、円の開始を描くのが遅くなり、上の部分が開いてしまいます。


使い方

1. この記事の最後に記したTestBaseViewクラスをプロジェクトに追加します。

2. テスト対象となるビューに、以下の3つのメソッドを追加します。locationに描画のための座標が入ってくるものとして、touchesBegan:withEvent等と同じ処理を記述してください。

- (void)touchesBegan:(CGPoint)location;
- (void)touchesMoved:(CGPoint)location;
- (void)touchesEnded:(CGPoint)location;

3. 以下のような感じでTestBaseViewを画面に追加します。(追加先はテスト対象ビューと異なってもよいです)

TestBaseView *testBaseView = [[TestBaseView alloc]
     initWithFrame:CGRectMake(0,0,320,100)];
[testBaseView autorelease];
[testBaseView setup];
[testBaseView setDrawView:drawView];     //このdrawViewを、アプリで使ってる描画用のビューにすること。
[self addSubview:testBaseView];    

TestBaseViewソース

テスト用なのでソース汚いです。すまんす。

#import <UIKit/UIKit.h>

#define TEST_NUMBERS_OF_POINT (10000)
#define TEST_NUMBERS_OF_DRAW_X (6)
#define TEST_NUMBERS_OF_DRAW_Y (2)
#define TEST_POINTS_IN_CIRCUL (60)
#define VIEW_WIDTH (320)
#define VIEW_HEIGHT (300)
#define CIRCUL_RADIUS (48)

#define TEST_FLAG_START (0)
#define TEST_FLAG_MOVE (1)
#define TEST_FLAG_END (2)

#define COUNT_SECOND (2.0)

@interface TestBaseView : UIView {
     UITextView *consol;
    
     CGPoint points[TEST_NUMBERS_OF_POINT];
     int pointFlags[TEST_NUMBERS_OF_POINT];
     int drawIndex[TEST_NUMBERS_OF_POINT];
     CGPoint pointOfEnd[1000];
     int countOfPoints;
     int countOfDraws;
     int pointIndex;
     int lastDrawIndex;
     BOOL isNextStart;
    
     id drawTestView;
     NSDate *startDate;
     int drawedPoints;
}
-(void)setup;
@end
#import "TestBaseView.h"

@implementation TestBaseView
-(void)drawPoint:(int)index {
     if (pointFlags[index]==TEST_FLAG_START) {
          [drawTestView touchesBegan:points[index]];
     }else if (pointFlags[index]==TEST_FLAG_MOVE) {
          [drawTestView touchesMoved:points[index]];
     }if (pointFlags[index]==TEST_FLAG_END) {
          [drawTestView touchesEnded:points[index]];
     }
}
-(void)endOfDraw {
     consol.text = [NSString stringWithFormat:@"points:%d",drawedPoints];
}
-(void)drawNext {
     double t = -[startDate timeIntervalSinceNow]; //double型で秒を返す(マイナス)
     drawedPoints ++;
    
     //COUNT_SECOND秒間でcountOfPointsまで描画するから、
     int index = t*countOfPoints/(double)COUNT_SECOND;
     if (index > countOfPoints-1) {
          if (isNextStart==NO) {
               //終了
               [drawTestView touchesEnded:pointOfEnd[lastDrawIndex]];
               [self endOfDraw];
          }
          return;
     }
    
     int dIndex = drawIndex[index];
     if (lastDrawIndex<dIndex) {
          //次の図になったので、前の図を終了
          if (isNextStart==NO) {
               [drawTestView touchesEnded:pointOfEnd[lastDrawIndex]];
               isNextStart=YES;
          }else{
               //次の図の開始
               [drawTestView touchesBegan:points[index]];
               isNextStart = NO;
               lastDrawIndex = dIndex;
          }
     }else {
          [drawTestView touchesMoved:points[index]];
     }
    
     [NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(drawNext) userInfo:nil repeats:NO];
}
-(void)start:(id)sender {
     NSLog(@"start");
     pointIndex = 0;
     isNextStart = YES;
     lastDrawIndex = 0;
     drawedPoints =0;
     [startDate release];
     startDate = [NSDate date];
     [startDate retain];
    
     [drawTestView touchesBegan:points[0]];
    
     [self drawNext];
}
-(void)dealloc {
     [startDate release];
     [consol release];
     [super dealloc];
}
-(void)setup {
     UIButton *startButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
     startButton.frame = CGRectMake(10, 30, 100, 30);
     [startButton setTitle:@"start" forState:UIControlStateNormal];
     [startButton addTarget:self action:@selector(start:) forControlEvents:UIControlEventTouchUpInside];
     [self addSubview:startButton];
    
     consol = [[UITextView alloc]initWithFrame:CGRectMake(0,60,320,40)];
     consol.opaque = YES;
     consol.backgroundColor = [UIColor whiteColor];
     consol.text = @"hoge";
     consol.alpha =0.5;
     consol.userInteractionEnabled = NO;
     [self addSubview:consol];
    
     // point のセットアップ 丸を格子状に並べて描画する
     int index = 0;
     int dIndex = 0;
     CGFloat radius = 48;
     CGFloat addX =(VIEW_WIDTH-(radius*2)-10)/TEST_NUMBERS_OF_DRAW_X;
     CGFloat addY =(VIEW_HEIGHT-(radius*2)-10)/TEST_NUMBERS_OF_DRAW_Y;
     for (int drawY=0;drawY<TEST_NUMBERS_OF_DRAW_Y;drawY++) {
          for (int drawX=0;drawX<TEST_NUMBERS_OF_DRAW_X;drawX++) {
               CGFloat startX = drawX * addX + radius+10;
               CGFloat startY = drawY * addY + radius+10;
              
               for (int i=0;i<TEST_POINTS_IN_CIRCUL ;i++) {
                    CGFloat x = startX-sin(2*M_PI* i/TEST_POINTS_IN_CIRCUL)*radius;
                    CGFloat y = startY-cos(2*M_PI* i/TEST_POINTS_IN_CIRCUL)*radius;
                    points[index] = CGPointMake(x, y);
                    drawIndex[index] = dIndex;
                    pointFlags[index] = TEST_FLAG_MOVE;//move
                    if (i==0) {
                         pointFlags[index]=TEST_FLAG_START;//start
                    }else if (i==TEST_POINTS_IN_CIRCUL-1) {
                         pointFlags[index]=TEST_FLAG_END;//end
                         pointOfEnd[dIndex] = CGPointMake(x, y);
                    }
                    index ++;
               }
               dIndex++;
          }
     }
     countOfPoints = index;
     countOfDraws =dIndex;
    
}
-(void)setDrawView:(id)view {
     drawTestView = view;
}
@end

VIEW_WIDTH、VIEW_HEIGHTは対象となるビューのサイズです。大きくすると、大きなビューを想定して丸の間隔が広がります。

ソースのCOUNT_SECONDは、シュミレートする時間(秒)です。ここを減らすと、もっとシビアな条件を測定できます(実際のSpeedTextのテスト時は1秒にしてます。画像は2秒のもの)

このテストプログラムでは、全体的に遅くても安定した速度で処理されると、いい結果になるので、描画自体の厳密な速度は測れないんですけどね。あと、touchが呼ばれる部分をスキップしちゃってるし。まあ、安定した速度で書けるかというテストですね。

hirogramhirogram 2010/08/18 09:53 TestBaseViewの-(void)drawPoint:(int)index と、メンバ変数pointFlagsへの代入処理は、不要でした。あっても動きますが、邪魔なら削ってください。

ゲスト



トラックバック - http://iphone-dev.g.hatena.ne.jp/hirogram/20100818