Quartz 2Dのテストプログラム


- source by Xcode


- drawRect:実装部 :

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextBeginPath(context);
    CGContextAddRect(context, CGRectMake(0, 0, 500, 500));
    float bg=(white)?1:0;
    CGContextSetRGBFillColor(context,bg, bg,  bg ,1);
    CGContextDrawPath(context, kCGPathFill);
    CGContextSetBlendMode(context, blendMode);
    for(int i=0;i<totalCount;i++){       
        Line *line=(Line *)[lines objectAtIndex:i];
        [line update];
        CGContextMoveToPoint(context, centerX,centerY);
        float brightness=grayscale?1:1;
        float saturation=grayscale?0:1;
        float hue=(float)i/totalCount*0.2+colorFlag;
        if(white)brightness=grayscale?0:1;
        UIColor *color=[UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1];
        CGContextSetStrokeColorWithColor(context, color.CGColor);
        CGContextSetLineWidth(context, [line getWeight]/8+0.1);
        CGContextAddCurveToPoint(context, line.c1.x, line.c1.y, line.c2.x, line.c2.y, line.c3.x, line.c3.y);
        CGContextDrawPath(context, kCGPathStroke);
    }

XcodeのHelpは、Flashなんかと比べると
とても良くできていて好きですが、
英語だから、頭が痛いので、メモ

ここも参考に、UIButtonに画像を設定して、アニメーション

●UIButtonに画像を設定

//使用するUIImageインスタンス作成
UIImage *normalImage = [ UIImage imageNamed : @"normal.png"];
UIImage *highlightImage = [UIImage imageNamed : @"highlight.png"];
UIImage *disableImage = [UIImage imageNamed : @"disable"];

UIImage *normalBgImage = [ UIImage imageNamed : @"normal.png"];
UIImage *highlightBgImage = [UIImage imageNamed : @"highlight.png"];
UIImage *disableBgImage = [UIImage imageNamed : @"disable"];

/*
UIButtonインスタンス作成
UIButtonTypeは、UIButton.hに
構造体で、
typedef enum {
   UIButtonTypeCustom = 0,
   UIButtonTypeRoundedRect,
   UIButtonTypeDetailDisclosure,
   UIButtonTypeInfoLight,
   UIButtonTypeInfoDark,
   UIButtonTypeContactAdd,
} UIButtonType;
0〜5の6つ定義されていた
*/
UIButton *imageBtn = [UIButton buttonWithType : UIButtonTypeCustom ];

//一番背面の色(backgroundImageよりも後ろ)
imageBtn.backgroundColor = [UIColor blackColor];
//ボタンのサイズと位置を指定 x=0 , y=0に、w=100,h=100の大きさを指定した
imageBtn.frame = CGRectMake(0,0,100,100);
//非表示
imageBtn.alpha=0;
//0に指定するとエラーがでることがあったので、0.1とかして限りなく小さくした
imageBtn.transform=CGAffineTransformMake(0.1,0.1);

/*
メイン画像を設定  : setImage:forState:
画像はリサイズされずに表示された
*/
[imageBtn setImage : normalImage forState : UIControlStateNormal ];
[imageBtn setImage : highlightImage forState : UIControlStateHighlighted];
//imageBtn.enabled=NO
[imageBtn setImage : disableImage forState : UIControlStateDisabled];

/*
次に、setImage:forState:で指定したボタンのエッジのマージンを一括調整した
正の整数のみ有効で、
normalImage , highlightImage , disableImageがリサイズされた
*/
imageBtn.imageEdgeInsets = UIEdgeInsetsMake(5,5,5,5);

/*
次に、背景画像を設定 : setBackgroundImage:forState:
すべてframeで指定したサイズにリサイズされた
*/
[imageBtn setImage : normalImage forState : UIControlStateNormal ];
[imageBtn setImage : highlightImage forState : UIControlStateHighlighted];
[imageBtn setImage : disableImage forState : UIControlStateDisabled];

/*
ラベルを設定 : setTitle:forState:
setImage:forStateと共に、使用すると下に隠れて見えなかったけど、
*/
[imageBtn setTitle:@"normal" forState : UIControlStateNormal];
[imageBtn setTitle:@"press" forState : UIControlStateHighlighted];
[imageBtn setTitle:@"disable" forState : UIControlStateDisabled];

/*
setImage画像もしくはsetTitleのラベルの位置を調整した
Left Center , Right ,Top,Bottomと指定したら、それっぽく調整された
*/
imageBtn.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
imageBtn.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;

//で、自分が保持しているviewに追加した
[self.view addSubView:imageBtn];

これで、画像をUIButtonに設定できた

●次に、このUIButtonを、scaleX=1 , scaleX=1 , x=10,y=10,alpha=1にアニメーション

/*
UIViewのクラスオブジェクトのメソッドを使用し、
まず動かす準備をした

ディレイ設定、
アニメーション時間設定、
アニメーションのタイプ設定
(※EaseInOut , ElaseIn , EaseOut,Linearの4つしか無いけど自作できるらしい)
このアニメーションのイベントを取得するために、delegateを自分に設定、
アニメーション開始イベントのセレクタ設定、
アニメーション終了イベントのセレクタ設定
*/
[UIView beginAnimations:@"imageBtnID" , context:nil];
[UIView setAnimationDelay:0];
[UIView setAnimationDuration:1];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDelegate:self];
[UIView setAnimationWillStartSelector : @selector(onAnimateStart:context:)];
[UIView setAnimationDidStopSelector : @selector(onAnimateEnd:finished:context)];

/*
次に目的の値に、btnImageの表示が変化するように設定するために、
移動とスケーリングに、アファイン変換を使用した
*/
btnImage.alpha=1;
btnImage.transform=CGAffineTransformTranslate(CGAffineTransformMakeScale(0,0) , 10,10);

//準備ができたので、アニメーションを要求した
[UIView commitAnimestions];

これで動いた

//2つの指定したdelegateしたセレクタもテストした
- (void) onAnimateStart : (NSString*)animationID context:(void *)nil{
  NSLog(@"Start Animation > Animation ID : %@",animationID);
}

- (void) onAnimateEnd : (NSString*)animationID finished:(BOOL) context:(void *)nil{
  NSLog(@"End Animation > Animation ID : %@",animationID)
}



HSBは、好きなのですが、Actionscriptには、
HSBは独自で実装する必要があって、
いつも使いたいときに、行き当たりばったりに書いていた
HSB⇔RGBの変換クラスを今更ながら書いたので、
いい加減な動作テスト。

> HSB to RGB

いっつも、いい加減にGraphicsでドローすると、画面がちらつく


> Sashamon

緩緩だなぁとSashamonを聞きながら、
wonderflを見ていたら、無性に手を動かしたくなり、
Processing > MetaballをFlashへ。

> Metaball

このMetaballのレンダリングテクニックは、
1980年頃、Each metaballという人によって紹介されたらしい。

> Metaball Code at wonderfl
> Mangrove World

オンライン植林コンテンツのFlash実装を担当しました。

Tweenerはあれだし、
期待の慣れないTweensyを使いましたが、
やはり慣れなかった。

無駄に下手に、
ガーベージコレクターを助けようと、
disposeメソッドで多くの必要なくなったプロパティを、
丁寧に、nullとかNaNとかにしていたつもりなのですが、
予想外のnullなのでエラーなのですよ。
みたいなエラーメッセージに悩まされ、
どこがだよ、すいません。

実際、flashで、ガーベージコレクションされる有効なdispose方法を
知ったかぶっている事が問題だと納品した翌日くらいに思った。
> PC 選択ガイド 「金のPC」

Flash実装を担当しました。


ミケランジェロの上半身の彫像は、
一般成人男性くらいの上半身の大きさなのですが、
撮影したすべての彫像を頂けるらしい。

イェイ。
removeChildとvisibile=falseが、
他に与える実行速度の影響をテストする小さなサンプル結果で、
それほど差がないとの記事を読んだ事がある。

じゃぁ、コンストラクタで、
必要になるすべてのDisplayObjectをaddChildし、
visible=falseで消してしまえとなんて安易に考えたりしてしまう。

ガーベージコレクションの対象にさせたい場合は、
当然、removeChildしか考えないから問題ない。

単純な例として、
10000個のDisplayObjectインスタンスを常に保持していて、
10個のみのDisplayObjectを画面上に表示し、
containsプロパティを調べ、
各深度を更新するようなプログラムの場合についてはどうか?

for(var i=0 ; i<target.contains.length;i++){
 setChildIndex(target.contains[i] , something);
}

この場合、visible=falseは、良くない。
removeChildしておいた方が、当然軽い。

DisplayObjectのvisibleプロパティは、
ただ単にそれ自身のプロパティを変更するだけに対し、
DisplayObejctContainerのremoveChildメソッドは、
そのDisplayObjectContainerが保持するプロパティも、
変更するという点を、意識して使い分ける必要があり、
これが、プロバティとメソッドの違いの特徴とも言える。
以前再帰による幅優先探索を紹介したが、
迷路が巨大になると、
スタックオーバーフローの問題に突き当たる。

再帰が駄目なんじゃなく、お前の再帰関数が駄目なんだよ、
とか言うけど、無視して、
この場合、再帰を使用して幅優先探索を実装することは、
向いていないと言わせてください。

解決策として、再帰では無く、
do whileのループを使用する方法が考えられる。

ただ、ループの上限を設定しない場合、
その他のプログラムの実行速度や描画に影響を及ぼす。

下記のようにループに対して、
最大ループ回数を設定し、
タイマーを使うことで、解決できる。

private var currentCount:uint;

private var searchRetryTimer:Timer=new Timer(100,1);

public function search(maxLoopCount:uint=1000){
     do{
       currentCount++;
       if(currentCount>maxLoopCount){
        searchRetryTimer.start();
        return;
       }
       checkGoal();
     }while(!founded);
    
     callBack.apply(scope , [goalInfo]);
}

private function onSearchRetryTimer(evt:Timer):void{
      search();   
}

Math.randomは、毎回同じ確率でランダム値を
返却するとても単純な優しいメソッドです。

しかし、このMath.randomメソッドは、非常に、柔軟で、パワーがあります。

Math.randomのようなコントラストの強いランダム現象は、
私たちの周りでは、あまり存在しません。

天候などの自然現象、人の成長や感情、生活、会話、また、
サウンドスペクトラムのビジュアライズ表示など、
ほとんどすべての物事は、コントラストが強い現象ではありません。

var a:Number=Math.random()*10000;
を100回ループ実行した出力結果、

1,500,9299,4,10,15,700,600,2090,1090,99,8829,,,,,,,,,,,,,

これは、非常にコンストラストが強い結果と言えます。



この現象は、私たちにとって異常です。



ActionScriptには、より自然なランダムメソッド、
BitmapData.perlinNoiseメソッドは存在しますが
BitmapDataに限らずより自然なランダムを、取得することが必要な機会は多いです。

より自然なランダムの意味は、drunkメソッドを知ることで理解できると思います。
より自然なランダム発生装置、drunkメソッドを紹介します。


> drunk method sample


上下のプログラムの違いは、
ENTER_FRAMEイベントリスナーメソッド内の
x,y,rgbのランダム値の取得の3行を、
drunkメソッドかMath.randomメソッドかに変更しただけです。
その他、すべてのコードが全く同じとは思えないランダムメソッドの威力です。


※どちらかといえば、
私たちの周りで起きている出来事すべては、上の自然なランダムdrunkメソッドで
表現されているものに近いと思います。


drunkメソッドも内部で、Math.random()を使用していて、
実は、Math.random同様とても単純で、作成も簡単です。


第一引数 : 0から指定した範囲のランダムな値を返却します。
第二引数 : 前のランダム値のオフセットを指定します。

例えば、
Randomクラスが、drunkメソッドを実装しているとします。

var rnd:Random=new Random();
var value:Number=rnd.drunk(100,10);

trace(value);

//output : 80

value=rnd.drunk(100,10);

このときのvalueの出力結果は、

70から90の間の乱数です。

これが,drunkメソッドです。
非常に単純なこのdrunkメソッドを使用するだけで、
上記のサンプルの結果が得られます。

このdrunkメソッドは、Max/Mspのdrunkオブジェクトを参考にしました。

> Max/Msp drunk method

このオブジェクトのdrunkという名前は、
数値の範囲の中で「酔っぱらいの千鳥足」のように
変化する値を返却することからこのように名づけられたらしいです。

Actionscriptで、迷路の最短距離の解が必要になり、

幅優先探索アルゴリズムを使用してみることに。

幅優先探索

下記の仕様で迷路配列を定義する。

// 0 : 道ID
// 1 : 壁ID

private const maze_ary:Array=[
[1,0,1,1,1,1,1,1],
[1,0,0,0,0,0,0,1],
[1,1,1,0,1,1,0,1],
[1,1,1,1,1,1,0,1]
]

// 5 : 探索済みID
private static const PASSED_ID:uint=5;


1. 探索を開始する。

まず空のキュー配列を作成し、

探索を開始する最初のマスのMassインスタンスを追加する。

※すべてのMassインスタンスは、
観測者、リレー走者みたいなものと、考えるとわかりやすい。
最初のMassインスタンスは、スタート走者とイメージできる。

迷路探索と最適解

※すべてのMassインスタンスは、
自分のマス情報であるrow , colプロパティと、
探索開始から、自分のマスにたどり着いたまでの
経路massList_aryを保持している。
つまり最初のMassインスタンス(スタート走者)に渡すmassList_aryは、空である。

※Massインスタンスは、コンストラクタで、
与えられたmassList_aryをコピーし、自分のMass情報
{row : row , col : col}を、コピーしたmassList_aryに追加する。


2.再帰処理を行う。

キュー配列からqueue_ary[0]のMassインスタンスを取り出す。

このインスタンスをcurrentMassと呼ぶとする。

currentMassのrow,colを調べ、左右上下で
キュー配列に追加すべき、移動可能なマスを探す。

ここでもし、移動可能なマスが、

すでに経由済みのマスであれば追加しない。

上記2つの条件を満たす場合、その移動可能なマスのMassインスタンスを作成し、
キュー配列に追加する。
この時、作成されるnextMassインスタンスのコンストラクタで、
currentMassインスタンスが保持している経由配列massList_aryを渡す。
これでnextMassインスタンスは、今までの経路を知っている。

(次の走者にバトンを渡す。バトンを渡す際は必ず、
自分に辿りつくまでに経由した各走者の位置配列を渡すことで、
現在の走者はどの経路を通ってバトンが渡ってきたか
を常に知っていることになる。)

左右上下調べ、キュー配列に、有効なMassインスタンスをすべて追加後、
maze_ary[currentMass.col-1][currentMass.row-1]=PASSED_ID
とし、すでに経由済みMassであることを記憶する。

不要になったcurrentMassインスタンスをキュー配列から削除し、
キュー配列に追加されているnextMassインスタンスに、委ねる。

ここでキュー配列を調べ、空でなければ 2. を繰り返す。


最初にゴールを見つけたMassインスタンスが、
最短経路massList_aryを保持していることになる。

ゴールが見つかった時点で、再帰処理をストップする。

※ここで重要なことは、
左右上下調べ、queue_aryに追加後
再帰処理を行うことだ。

でなければ、迷路に広いスペースがあった場合、
必ずしも最短距離を導き出してくれるとは限らない。



以上を、Actionscriptでプログラミングする。



----------------------------------------------------------------


MazeConfigクラスは、迷路の定数などを保持する。
MazeDataクラスは、迷路状況(どのマスに今いるかなど)
とMazeConfigクラスの一部のデータを保持する。
Massクラスは、MazeUtilsの内部クラス。


package utils
{

    import data.MazeConfig;
    import data.MazeData;
  
    public class MazeUtils
    {
       
        public static var queue_ary:Array;
       
        public static var maze_ary:Array;
       
        public static var COL:uint=MazeData.COL;
       
        public static var ROW:uint=MazeData.ROW;
       
        public static var goalRow:uint=MazeData.goalRow;
       
        public static var goalCol:uint=MazeData.goalCol;
       
        public static var startRow:uint=MazeData.startRow;
       
        public static var startCol:uint=MazeData.startCol;
       
        private static var goalInfo:Array;
       
        public static var PASSED_ID:uint=5;
       
        /**
         * 現在の位置からゴールまでの最短距離の配列を返却します。
         *
         * @return
         *
         */       
        public static function get goalList():Array{
            goalInfo=null;
           
            startRow=MazeData.currentRow;
            startCol=MazeData.currentCol;
           
            maze_ary=MazeData.copyInfo;
           
            queue_ary=[];
            queue_ary[0]=new Mass(startRow , startCol , []);
           
            check();
           
            return goalInfo;
        }
           
        private static function check():void{
            var currentMass:Mass=queue_ary[0];
            queue_ary.shift();
          
           if(maze_ary[currentMass.col-1][currentMass.row-1]==PASSED_ID){
               return;
           }

            maze_ary[currentMass.col-1][currentMass.row-1]=PASSED_ID;
           if(goalInfo!=null){
                   //最短距離が見つかっているので、残りのmassでは、何もしない。
            }else if(currentMass.isGoal){
                   //ゴールの最短距離を見つけた。
                goalInfo=currentMass.massList;
            }else{
                var nextMass:Mass;   
               
                //前方
                if(needCheck(currentMass.row , currentMass.col+1)){           
                    nextMass=new Mass(currentMass.row , currentMass.col+1 , currentMass.massList);
                    queue_ary.push(nextMass);
                    //ここで、check関数を実行してしまうと最短距離でなくなる可能性がある。
                }
               
                //後方
                if(needCheck(currentMass.row , currentMass.col-1)){
                    nextMass=new Mass(currentMass.row , currentMass.col-1 , currentMass.massList);
                    queue_ary.push(nextMass);
                }
               
                //左方
                if(needCheck(currentMass.row-1 , currentMass.col)){
                    nextMass=new Mass(currentMass.row-1 , currentMass.col , currentMass.massList);
                    queue_ary.push(nextMass);
                }
               
                //右方
                if(needCheck(currentMass.row+1 , currentMass.col)){
                    nextMass=new Mass(currentMass.row+1 , currentMass.col , currentMass.massList);
                    queue_ary.push(nextMass);
                }

                currentMass=null;

                if(queue_ary.length==0){
                    trace("not founded");
                }else{
                    check(queue_ary);
                }
            }
           
           
        }
       
        /**
         * 一度も通っていない場合 &&
         * マスが存在する &&
         * 道である場合、trueを返却します。
         *
         * @param row
         * @param col
         * @return
         *
         */       
        private static function needCheck(row , col):Boolean{
            if(row>1&&row<=ROW&&col>1&&col<=COL){
                return maze_ary[col-1][row-1]==MazeConfig.ROAD_ID;
            }
            return false;
        }
    }
}

/**
 * マス情報と経由配列を保持する内部クラス(観測者)
 *
 * @author
 *
 */
import utils.MazeUtils;

class Mass{
    private var mass_ary:Array;
    private var maze_ary:Array;
   
    private var _row,_col:uint;
   
    public function Mass(row:uint,col:uint,list:Array ){
        this.mass_ary=[];

        this._row=row;
        this._col=col;
       
        var len=list.length;
        for(var i=0;i<len;i++){
            mass_ary.push(list[i]);
        }
       
        this.mass_ary.push({row:_row , col:_col});

    }
   
    public function get massList():Array{
        return mass_ary;
    }
   
    public function get isGoal():Boolean{
        return row==MazeUtils.goalRow&&col==MazeUtils.goalCol;
    }

    public function get row():uint{
        return _row;
    }
   
    public function get col():uint{
        return _col;
    }
}





高速化など考えると、多々改善の余地はありし。
知らんけど。