In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

文字のアウトライン抽出(処理最適化)

1. 左上の入力欄に、好きな文字を一文字入力
2. パラメータで激しさ変更
3. Clickで静止
4. 右上チェックボックスでパス表示

※少しづつバグを修正
■ 文字内のパスの色を白に
■ パスの取得方法

【修正】
■ adjustmentPath:パスの最適化
■ エッジ取得方法(簡素化):2値化追加、余分な処理削除

http://linktale.net/

【参考】
http://jsdo.it/tsmallfield/stars2
http://wonderfl.net/c/1uJu/read
Get Adobe Flash player
by linktale 28 May 2012
/**
 * Copyright linktale ( http://wonderfl.net/user/linktale )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/uCTv
 */

/*
1. 左上の入力欄に、好きな文字を一文字入力
2. パラメータで激しさ変更
3. Clickで静止
4. 右上チェックボックスでパス表示

※少しづつバグを修正
■ 文字内のパスの色を白に
■ パスの取得方法

【修正】
■ adjustmentPath:パスの最適化
■ エッジ取得方法(簡素化):2値化追加、余分な処理削除

http://linktale.net/

【参考】
http://jsdo.it/tsmallfield/stars2
http://wonderfl.net/c/1uJu/read
*/
package {
    import com.bit101.components.CheckBox;
    import com.bit101.components.HUISlider;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFieldType;
    import flash.text.TextFormat;
    import flash.text.TextFormatAlign;
    
    [SWF(width="465", height="465", frameRate="48")]
    
    public class Index extends Sprite {
        
        private static var DEBUG:Boolean = false;
        private static var DEFAULT_TEXT:String = "夏";
        private static var TEXT_INPUT_AREA_H:Number = 30;        
        
        private var _updateFlg:Boolean = true; //描画フラグ
        
        private var _fontPlay:FontPlay; //文字解析クラス
        private var _pathBitmap:Bitmap; //パスのビットマップ
        
        private var _clickArea:Sprite; //クリックの範囲
        private var _pathArea:Sprite; //パスの描画領域
        private var _pathViewArea:Sprite = new Sprite();  //パスの描画領域
        private var _viewTextArea:Sprite = new Sprite(); //デバッグ用
        
        private var _basePathLists:Array = []; //パスの元位置
        private var _updatePathLists:Array = []; //パスの現位置
        
        private var _param1:Number = 1500;
        
        public function Index():void {
            trace("Class : Index");
            stage ? init() : addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init():void {
            trace("Method : init");
            
            //Wonderfl.capture_delay(10);
            Wonderfl.disable_capture();
            
            createInputTextArea(); //文字入力エリア生成
            if(DEBUG){ createViewTextArea(); }
            createFontPlay(); //文字解析クラス生成
            createEdge(); //エッジ生成
            createPath(); //パス生成
            setPathList(); //パスリスト初期化
            createController(); //コントローラ生成
            createClickArea(); //クリックエリア設定

            addEventListener(Event.ENTER_FRAME, update);
            _clickArea.addEventListener(MouseEvent.CLICK, click);
        }
        
        private function update(e:Event = null):void {
            //この関数を変更すると動きを変えられる
            
            var point:Point;
            var x:Number, y:Number, ax:Number, ay:Number, mouseXL:Number, mouseYL:Number, theta:Number, dist:Number;
            var mouseDiffX:Number = (stage.stageWidth / 2 - _fontPlay.textField.width / 2);
            var mouseDiffY:Number = (stage.stageHeight / 2 - _fontPlay.textField.height / 2 + TEXT_INPUT_AREA_H);
            for (var i:uint=0; i < _updatePathLists.length; i++ ) {
                for(var j:uint=0; j < _updatePathLists[i].length; j++ ) {
                    x = _updatePathLists[i][j].x;
                    y = _updatePathLists[i][j].y;
                    
                    mouseXL = mouseX - mouseDiffX;
                    mouseYL = mouseY - mouseDiffY;
                    
                    theta = Math.atan2(y - mouseYL, x - mouseXL);
                    dist = _param1 / Math.sqrt( ( mouseXL - x ) * ( mouseXL - x ) + ( mouseYL - y ) * ( mouseYL - y ) );
                    dist = dist > 10 ? 4 : dist; 
                    
                    ax = (Math.cos(theta) * dist + (_basePathLists[i][j].x - x) * 0.15) * 0.4;
                    ay = (Math.sin(theta) * dist + (_basePathLists[i][j].y - y) * 0.15) * 0.4
                    _updatePathLists[i][j].x += ax;
                    _updatePathLists[i][j].y += ay;
                }
            }
            
            _fontPlay.drawLines(_updatePathLists);
        }
        
        private function click(e:MouseEvent):void {
            if(_updateFlg){
                removeEventListener(Event.ENTER_FRAME, update);
                _updateFlg = false;
            }else {
                addEventListener(Event.ENTER_FRAME, update);
                _updateFlg = true;
            }
        }
        
        private function createInputTextArea():void {
            trace("Method : createInputTextArea");
            //Config
            var textFieldH:Number = 20, textFieldW:Number = 25;
            
            //BackGround
            var textInputArea:Sprite = new Sprite();
            textInputArea.graphics.beginFill(0xdddddd, 1);
            textInputArea.graphics.drawRect(0, 0, stage.stageWidth, TEXT_INPUT_AREA_H);
            textInputArea.graphics.endFill();
            addChild(textInputArea);
            
            //TextFild
            var textField:TextField = new TextField();
            textField.defaultTextFormat = new TextFormat("小塚ゴシック Pro L", 14, 0x000000, true,null,null,null,null,TextFormatAlign.CENTER);
            textField.x = textField.y = (TEXT_INPUT_AREA_H - textFieldH) / 2;
            textField.width = textFieldW;
            textField.height = textFieldH;
            
            textField.type = TextFieldType.INPUT;
            textField.maxChars = 1;
            textField.border = textField.background = true;
            textField.borderColor = 0x222222;
            textField.backgroundColor = 0xffffff;
            textField.text = DEFAULT_TEXT;
            addChild(textField);
            
            //Event
            textField.addEventListener(Event.CHANGE, function(e:Event):void { onChange(textField.text, e);  } );
        }
        
        private function onChange(text:String, e:Event=null):void {
            trace("Method : onChange");
            if(text){
                _fontPlay.change(text);
                createEdge();
                createPath();
                setPathList();
            }
        }
        
        private function createViewTextArea():void {
            //debug
            trace("Method : createViewTextArea");
            _viewTextArea.y = TEXT_INPUT_AREA_H;
            _viewTextArea.graphics.beginFill(0xffffff);
            _viewTextArea.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight-TEXT_INPUT_AREA_H);
            _viewTextArea.graphics.endFill();
            _viewTextArea.width = stage.stageWidth;
            _viewTextArea.height = stage.stageHeight - TEXT_INPUT_AREA_H;
            addChild(_viewTextArea);
        }
        
        private function createFontPlay():void {
            trace("Method : createFontPlay");
            _fontPlay = new FontPlay(DEFAULT_TEXT);
            
            //debug
            if(DEBUG){
                var textField:TextField = _fontPlay.textField;
                textField.x = stage.stageWidth / 2 - textField.width / 2;
                textField.y = stage.stageHeight / 2 - textField.height / 2;
                _viewTextArea.addChild(textField);
            }
        }
        
        private function createEdge():void {
            trace("Method : createEdge");
            if (_pathBitmap) { _pathViewArea.removeChild(_pathBitmap); } else { addChild(_pathViewArea); }
            
            _pathBitmap = new Bitmap(_fontPlay.pathBitmapData);
            _pathBitmap.x = stage.stageWidth / 2 - _fontPlay.textField.width / 2;
            _pathBitmap.y = stage.stageHeight / 2 - _fontPlay.textField.height / 2 + TEXT_INPUT_AREA_H;
            _pathBitmap.alpha = 0.2;
            _pathViewArea.addChild(_pathBitmap);
        }
        
        private function createPath():void {
            trace("Method : createPath");
            if (_pathArea) { _pathViewArea.removeChild(_pathArea); }
            
            _pathArea = _fontPlay.pathArea;
            _pathArea.x = stage.stageWidth / 2 - _fontPlay.textField.width / 2;
            _pathArea.y = stage.stageHeight / 2 - _fontPlay.textField.height / 2 + TEXT_INPUT_AREA_H;
            
            _pathViewArea.addChild(_pathArea);
        }
        
        private function setPathList():void {
            _basePathLists = _fontPlay.basePointLists.concat();
            _updatePathLists = [];
            for (var i:uint = 0; i < _basePathLists.length; i++ ) {
                _updatePathLists[i] = []
                for (var j:uint = 0; j < _basePathLists[i].length; j++ ) {
                    _updatePathLists[i][j] = new Point(_basePathLists[i][j].x, _basePathLists[i][j].y);
                }
            }
        }
        
        private function createClickArea():void {
            //Click Area
            _clickArea = new Sprite();
            _clickArea.graphics.beginFill(0x000000, 0);
            _clickArea.graphics.drawRect(0, TEXT_INPUT_AREA_H, stage.stageWidth, stage.stageHeight - TEXT_INPUT_AREA_H);
            _clickArea.graphics.endFill();
            addChild(_clickArea);
        }
        
        private function createController():void {        
            var slider:HUISlider = new HUISlider(this, 40, 7, "Param1", onSlide1);
            slider.width = 420;
            slider.minimum = 0;
            slider.maximum = 2000;
            slider.value = _param1;
            slider.labelPrecision = 0;
            
            var checkBox:CheckBox = new CheckBox(this, 442, 11, "", onCheck);
        }
        
        private function onSlide1(e:Event):void {
            _param1 = e.target.value;
        }
        
        private function onCheck(e:Event):void {
            _fontPlay.viewPathFlg = e.target.selected;
        }
    }
}






import flash.display.BitmapData;
import flash.display.GraphicsPathCommand;
import flash.display.GraphicsPathWinding;
import flash.display.Sprite;
import flash.filters.ConvolutionFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;

class FontPlay {
    
    private var _text:String= "";
    private var _textField:TextField = new TextField();
    
    private var _pathArea:Sprite = new Sprite();
    private var _bitmapData:BitmapData;
    private var _pathBitmapData:BitmapData;
    
    private var _edgeList:Array = [];
    private var _commandsLists:Array = [];
    private var _basePointLists:Array = [];
    private var _pathLists:Array = [];

    private var _viewPathFlg:Boolean = false;
    
    public function FontPlay(string:String):void {
        trace("Class : FontPlay");
        
        _text = string;
        //解析
        analyze(_text);
    }
    
    public function analyze(str:String):void {
        trace("Method : analyze");
        
        //TextFiled生成
        createTextFiled(str);
        //パスデータ生成
        createPathLists();
    }
    
    private function createTextFiled(str:String):void {
        trace("Method : createTextFiled");
        //TextFild
        _textField = new TextField();
        _textField.defaultTextFormat = new TextFormat("小塚ゴシック Pro L", 127, 0x000000, true,null,null,null,null,TextFormatAlign.CENTER);
        //_textField.defaultTextFormat = new TextFormat("", 127, 0x000000, true,null,null,null,null,TextFormatAlign.CENTER);
        _textField.text = str;
        _textField.autoSize = TextFieldAutoSize.LEFT;
        _textField.scaleX = _textField.scaleY = 2;
    }
    

    private function createPathLists():void {
        //文字のビットマップ生成
        createBitmapData();
        //ビットマップの解析
        analyzeBitmapData();
        //エッジのビットマップデータ生成
        crateEdgeBitmapData(_edgeList);
        //パスデータの生成
        createPath(_edgeList);
    }
    
    private function createBitmapData():void {
        trace("Method : createBitmapData");
        
        var scale:Number = 2.0;
        var matrix:Matrix = new Matrix(scale, 0, 0, scale);
        _bitmapData = new BitmapData(_textField.width*scale, _textField.height*scale, true);
        _bitmapData.draw(_textField, matrix);
        //2値化
        _bitmapData.threshold(_bitmapData, new Rectangle(0, 0, _bitmapData.width, _bitmapData.height), new Point(0, 0), "<", 0x00ffffff, 0xff000000, 255, false);
    }
    
    private function analyzeBitmapData():void {
        trace("Method : analyzeBitmapData");
        _edgeList = [];
        var edgeBitmapData:BitmapData = edgeDetection(_bitmapData);
        
        var color:uint;
        for (var x:uint = 0; x < edgeBitmapData.width; x++ ) {
            _edgeList[x] = [];
            for (var y:uint = 0; y < edgeBitmapData.height; y++ ) {
                color = edgeBitmapData.getPixel(x, y);
                if (color != 0x000000) {
                    _edgeList[x][y] = 0x000000;
                }else {
                    _edgeList[x][y] = 0xffffff;
                }
            }
        }
    }
    
    private function edgeDetection(bitmapData:BitmapData):BitmapData {
        //エッジ抽出
        var edgeBitmapData:BitmapData = new BitmapData(bitmapData.width, bitmapData.height, true);
        var cf1:ConvolutionFilter = new ConvolutionFilter(3, 3, [ 0, 1, 0, 1, -4, 1, 0, 1, 0]);
        //var cf2:ConvolutionFilter = new ConvolutionFilter(3, 3, [ -1, -1, -1, -1, 8, -1, -1, -1, -1]);
        
        edgeBitmapData.applyFilter(bitmapData, bitmapData.rect, new Point(), cf1); 
        //edgeBitmapData.applyFilter(edgeBitmapData, edgeBitmapData.rect, new Point(), cf2); 
        
        return edgeBitmapData;
    }
    
    private function crateEdgeBitmapData(edgeList:Array):void {
        trace("Method : cratePathList");
        
        _pathBitmapData = new BitmapData(_bitmapData.width, _bitmapData.height, false);
        _pathBitmapData.lock();
        for (var x:uint = 0; x < edgeList.length; x++ ) {
            for (var y:uint = 0; y < edgeList[x].length; y++ ) {
                _pathBitmapData.setPixel(x, y, edgeList[x][y]);
            }
        }
        _pathBitmapData.unlock();
    }
    
    private function createPath(edgeList:Array):void {
        trace("Method : createPath");
        var count:uint = 0;
        _pathLists = [];
        _pathLists[count] = [];
        
        var currentEdge:Point = getFirstPath(edgeList);
        if (currentEdge) {
            _pathLists[count].push(currentEdge);
        }
        
        while(true){
            var nextPoint:Point = getNext(edgeList, currentEdge);
            if (nextPoint) {
                _pathLists[count].push(nextPoint);
                edgeList[nextPoint.x][nextPoint.y] = 0xffffff;
                currentEdge = nextPoint;
            }else {
                currentEdge = getFirstPath(edgeList);
                if (currentEdge) {
                    count++;
                    _pathLists[count] = [];
                    _pathLists[count].push(currentEdge);
                    //break;
                }else {
                    break;
                }
            }
        }
        
        //パスデータの調整
        _pathLists = adjustmentPath(_pathLists, 0.7);
        
        _basePointLists = _pathLists;
        drawLines(_pathLists);
    }
    
    private function adjustmentPath(pathLists:Array, threshold:Number=0):Array {
        trace("Method : adjustmentPath");
        
        var newList:Array = [];
        var preList:Array = [];
        var preAngle:Number, nowAngle:Number;
        
        for (var i:uint = 0; i < pathLists.length; i++ ) {
            if (pathLists[i].length < 3) {
                newList[i] = pathLists[i];
                continue;
            }
            
            newList[i] = [];
            preList = [];
            preList[0] = pathLists[i][0];
            preList[1] = pathLists[i][1];
            preAngle = Math.atan2(preList[0].y - preList[1].y, preList[0].x - preList[1].x);
            newList[i].push(pathLists[i][0]);
            for (var j:uint = 1; j < pathLists[i].length; j++ ) {
                var p:Point;
                if (j == pathLists[i].length - 1) {
                    nowAngle = Math.atan2(pathLists[i][0].y-pathLists[i][j].y, pathLists[i][0].x-pathLists[i][j].x);
                    p = pathLists[i][0];
                }else{
                    nowAngle = Math.atan2(pathLists[i][j].y - pathLists[i][j+1].y, pathLists[i][j].x - pathLists[i][j+1].x);
                    p = pathLists[i][j + 1];
                }
                
                //角度が大きいPathを残す
                //斜め線の処理追加予定
                if (Math.abs(Math.abs(nowAngle) - Math.abs(preAngle)) > threshold) {
                    newList[i].push(p);
                    preAngle = Math.abs(nowAngle);
                }
                
            }
            
        }
        return newList;
    }
    
    //開始点を取得
    private function getFirstPath(edgeList:Array):Point {
        var firstEdge:Point;
        
        out :for (var x:uint = 0; x < edgeList.length; x++ ) {
            for (var y:uint = 0; y < edgeList[x].length; y++ ) {
                if (edgeList[x][y] == 0x000000) {
                    firstEdge = new Point(x, y);
                    edgeList[x][y] = 0xffffff;
                    break out;
                }
            }
        }
        
        return firstEdge;
    }
    
    //次の点を取得
    private function getNext(edgeList:Array, currentPoint:Point):Point {
        //エッジ抽出の精度が悪いので、それなりの範囲を調査
        var nextPoint:Point;
        var x:uint = currentPoint.x;
        var y:uint = currentPoint.y;
        
        if(edgeList[x - 1][y - 1]==0x000000){
            nextPoint = new Point(x - 1, y - 1);
        }else if (edgeList[x][y - 1]==0x000000) {
            nextPoint = new Point(x, y - 1);
        }else if (edgeList[x + 1][y - 1]==0x000000) {
            nextPoint = new Point(x + 1, y - 1);
        }else if (edgeList[x + 1][y]==0x000000) {
            nextPoint = new Point(x + 1, y);
        }else if (edgeList[x + 1][y + 1] == 0x000000) {
            nextPoint = new Point(x + 1, y + 1);
        }else if (edgeList[x][y + 1]==0x000000) {
            nextPoint = new Point(x, y + 1);
        }else if (edgeList[x - 1][y + 1]==0x000000) {
            nextPoint = new Point(x - 1, y + 1);
        }else if (edgeList[x - 1][y] == 0x000000) {
            nextPoint = new Point(x - 1, y);
        }
                
        return nextPoint;
    }
    
    //複数パスの線の描画
    public function drawLines(pathLists:Array):void {
        _pathArea.graphics.clear();
        
        for (var i:uint = 0; i < pathLists.length; i++ ){
            var datas:Array = createGraphicCommands(pathLists[i]);
            drawLine(datas);
        }
    }
    
    //GraphicCommandsを設定
    private function createGraphicCommands(pathList:Array):Array {
        _commandsLists = [];
        
        var commands:Vector.<int> = new Vector.<int>();
        var anchor:Vector.<Number> = new Vector.<Number>();
        
        for (var i:uint = 0; i < pathList.length; i++ ) {
            anchor.push(pathList[i].x, pathList[i].y);
            
            if (i == 0) {
                commands[0] = GraphicsPathCommand.MOVE_TO;
            }else {
                commands[i] = GraphicsPathCommand.LINE_TO;
            }
        }
        anchor.push(pathList[0].x, pathList[0].y);
        commands[i] = GraphicsPathCommand.LINE_TO;
        
        _commandsLists[0] = commands;
        _commandsLists[1] = anchor;
        
        return _commandsLists;
    }

    //線の描画
    private function drawLine(datas:Array):void {
        _pathArea.graphics.beginFill(0x000000, 1);
        _pathArea.graphics.lineStyle(1, 0xffffff, 0.8);
        _pathArea.graphics.drawPath(datas[0], datas[1], GraphicsPathWinding.NON_ZERO);
        _pathArea.graphics.endFill();
        _pathArea.graphics.lineStyle();
        
        if(_viewPathFlg){
            var radius:Number = 2;
            for (var i:uint = 0; i < datas[1].length; i += 2) {
                _pathArea.graphics.beginFill(0xdd4422, 1.0);
                _pathArea.graphics.drawCircle( datas[1][i]-radius/2, datas[1][i+1]-radius/2, radius);
                _pathArea.graphics.endFill();
            }
        }
    }
    
    //文字変更
    public function change(str:String):void {
        analyze(str);
    }
    
    public function get textField():TextField { return _textField; }
    
    public function get pathBitmapData():BitmapData { return _pathBitmapData; }
    
    public function get pathArea():Sprite { return _pathArea; }
    
    public function get commandsLists():Array { return _commandsLists; }
    
    public function get basePointLists():Array { return _basePointLists; }
    
    public function set viewPathFlg(value:Boolean):void { _viewPathFlg = value;    }
    
}