ひらがなリッパー(Hiragana ripper)

by keim_at_Si forked from Palm Graffiti Alphabet Ripper (diff: 85)
マウスジェスチャで適当なひらがなを書いてください.そのひらがなが切り取られます.ただし書き順に注意.
blog; 技術的解説:http://d.hatena.ne.jp/keim_at_Si/20110412/p1
webpage; http://soundimpulse.sakura.ne.jp/hiragana-ripper/
♥40 | Line 349 | Modified 2011-04-11 23:23:33 | MIT License
play

ActionScript3 source code

/**
 * Copyright keim_at_Si ( http://wonderfl.net/user/keim_at_Si )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/zKpY
 */

package {
    import flash.display.*;
    import flash.events.*;
    import flash.text.*;
    import flash.utils.*;
    
    public class main extends Sprite {
        public const DETECTION_TIMEOUT:int = 1000;
        
        public var gesturePad:GesturePad;
        public var gestureAnalyzer:GestureAnalyzer = new GestureAnalyzer();
        public var tf:TextField = new TextField();
        public var detected:TextField = new TextField();
        public var detectionTimer:Timer = new Timer(DETECTION_TIMEOUT, 1);
        
        function main() {
            graphics.beginFill(0xc0c0d0);
            graphics.drawRect(0,0,465,465);
            graphics.endFill();
            gesturePad = new GesturePad(this, 32, 16, 400, onMouseDown, onMouseUp);
            addEventListener(Event.ADDED_TO_STAGE, setup);
            
            // 432
            // 5*1
            // 678
            gestureAnalyzer.map("あ", "10078007654321876");
            gestureAnalyzer.map("い", "78008");
            gestureAnalyzer.map("う", "18001876");
            gestureAnalyzer.map("え", "180016218781");
            gestureAnalyzer.map("お", "10076543218765008");
            gestureAnalyzer.map("か", "187600760087");
            gestureAnalyzer.map("き", "10010080081");
            gestureAnalyzer.map("く", "68");
            gestureAnalyzer.map("け", "7200210076");
            gestureAnalyzer.map("こ", "1800781");
            gestureAnalyzer.map("さ", "21007800781"); //せ
            gestureAnalyzer.map("し", "7812");
            gestureAnalyzer.map("す", "1007654321876"); //よ
            gestureAnalyzer.map("せ", "1007600781");
            gestureAnalyzer.map("そ", "1612678");
            gestureAnalyzer.map("た", "1006700210081");
            gestureAnalyzer.map("ち", "100621876");
            gestureAnalyzer.map("つ", "21876");
            gestureAnalyzer.map("て", "12678");
            gestureAnalyzer.map("と", "80056781");
            gestureAnalyzer.map("な", "100600850076543218"); //ま
            gestureAnalyzer.map("に", "700100781");
            gestureAnalyzer.map("ぬ", "8007654321876543218");
            gestureAnalyzer.map("ね", "70012621876543218");
            gestureAnalyzer.map("の", "654321876");
            gestureAnalyzer.map("は", "7200210076543218");
            gestureAnalyzer.map("ひ", "1267812381");
            gestureAnalyzer.map("ふ", "85007876540078008");
            gestureAnalyzer.map("へ", "218");
            gestureAnalyzer.map("ほ", "7200210010076543218");
            gestureAnalyzer.map("ま", "10010076543218");
            gestureAnalyzer.map("み", "126543218006");
            gestureAnalyzer.map("む", "1007654321878123008");
            gestureAnalyzer.map("め", "8007654321876");
            gestureAnalyzer.map("も", "78123001001");
            gestureAnalyzer.map("や", "21876500700800"); //か
            gestureAnalyzer.map("ゆ", "73218765400876");
            gestureAnalyzer.map("よ", "10076543218");
            gestureAnalyzer.map("ら", "8500621876"); //ち
            gestureAnalyzer.map("り", "72001876"); //い
            gestureAnalyzer.map("る", "12621876543218");
            gestureAnalyzer.map("れ", "7001262678");
            gestureAnalyzer.map("ろ", "12621876");
            gestureAnalyzer.map("わ", "70012621876");
            gestureAnalyzer.map("を", "10062187006781");
            gestureAnalyzer.map("ん", "621812");
            gestureAnalyzer.map("1", "27", 0, 7);
            gestureAnalyzer.map("2", "18761", 0, 8);
            gestureAnalyzer.map("3", "1876518765");
            gestureAnalyzer.map("4", "610067");
            gestureAnalyzer.map("5", "718765001");
            gestureAnalyzer.map("6", "67812345", 0, 5);
            gestureAnalyzer.map("7", "16", 0, 7);
            gestureAnalyzer.map("7'", "60016", 0, 7);
            gestureAnalyzer.map("8", "5678765432");
            gestureAnalyzer.map("9", "5678126");
            gestureAnalyzer.map("0", "67812345", 0, 3);
            
            tf.width = 465;
            tf.height = 32;
            addChild(tf);
        }
        
        public function setup(e:Event) : void {
            e.target.removeEventListener(e.type, arguments.callee);
            detectionTimer.addEventListener(TimerEvent.TIMER, function(e:TimerEvent):void { detect(); });
            detectionTimer.stop();
        }
        
        public function onMouseDown() : void {
            detectionTimer.stop();
        }
        
        public function onMouseUp() : void {
            detectionTimer.reset();
            detectionTimer.start();
        }
        
        public function detect() : void {
            var candidate:Array = gestureAnalyzer.analyze(gesturePad.head);
            tf.text = gestureAnalyzer.pattern.join("") + " / " + candidate.join(" ");
            gesturePad.textField.text = candidate[0].charAt(0);
            gesturePad.flush();
        }
    }
}




import flash.display.*;
import flash.filters.*;
import flash.events.*;
import flash.geom.*;
import flash.text.*;
import com.bit101.components.*;
import org.libspark.betweenas3.*;
import org.libspark.betweenas3.easing.*;


class TextDroppingField extends Sprite {
    public var surface:Sprite, front:Bitmap, field:Sprite, back:Bitmap;
    public var frontBase:BitmapData, textBitmap:BitmapData;
    public var textField:TextField = new TextField();
    public var bigShadow:DropShadowFilter = new DropShadowFilter(6, 45, 0, 0.375, 16, 16);
    public var littleShadow:DropShadowFilter = new DropShadowFilter(3, 45, 0, 0.375, 8, 8);
    
    function TextDroppingField(width:int, height:int) {
        super();
        var s:Shape = new Shape(), g:Graphics = s.graphics, mat:Matrix = new Matrix(), i:int;
        surface = new Sprite();
        field = new Sprite();
        front = new Bitmap(new BitmapData(width, height, true, 0xffffffff));
        back = new Bitmap(new BitmapData(width, height, false, 0xffffff));
        textBitmap = new BitmapData(width, height, false, 0);
        frontBase = new BitmapData(width, height, false, 0);
        g.clear();
        g.beginFill(0xf0f0f0);
        g.moveTo(-8, 0);
        g.lineTo(0, 8);
        g.lineTo(8, 0);
        g.lineTo(0, -8);
        g.endFill();
        for (mat.ty=-((height&15)+16)*0.5; mat.ty<height; mat.ty+=16) 
            for (mat.tx=-((width&15)+16)*0.5; mat.tx<width; mat.tx+=16) 
                back.bitmapData.draw(s, mat);
        frontBase.fillRect(new Rectangle(0, 0, width, height), 0xffcccccc);
        frontBase.fillRect(new Rectangle(2, 2, width-4, height-4), 0xffffffff);
        g.clear();
        g.lineStyle(1, 0xeeeeee);
        for (i=1; i<16; i++) {
            g.moveTo(2,       height * 0.0625 * i);
            g.lineTo(width-2, height * 0.0625 * i);
            g.moveTo(width * 0.0625 * i, 2);
            g.lineTo(width * 0.0625 * i, height-2);
        }
        frontBase.draw(s);
        frontBase.fillRect(new Rectangle(width*0.5-1, 0, 2, height), 0xffcccccc);
        frontBase.fillRect(new Rectangle(0, height*0.5-1, width, 2), 0xffcccccc);
        front.bitmapData.copyPixels(frontBase, frontBase.rect, frontBase.rect.topLeft);
        textField.defaultTextFormat = new TextFormat("_sans", 64, 0xff0000, null, null, null, null, null, "center");
        textField.autoSize = "center";
        front.filters = [bigShadow];
        addChild(back);
        addChild(field);
        addChild(front);
        addChild(surface);
    }
    
    public function set text(str:String) : void {
        var frontPixels:BitmapData = front.bitmapData,
            w:Number = front.width, h:Number = front.height;
        textField.text = str;
        textBitmap.fillRect(textBitmap.rect, 0x00ff00);
        textBitmap.draw(textField, new Matrix(h*0.015625,0,0,h*0.015625,-w*0.275,-h*0.075));
        frontPixels.copyPixels(frontBase, frontBase.rect, frontBase.rect.topLeft);
        frontPixels.copyChannel(textBitmap, textBitmap.rect, textBitmap.rect.topLeft, 2, 8);
        
        var textParticlePixels:BitmapData = new BitmapData(w, h, true, 0xffffffff);
        textParticlePixels.copyPixels(frontBase, frontBase.rect, frontBase.rect.topLeft);
        textParticlePixels.copyChannel(textBitmap, textBitmap.rect, textBitmap.rect.topLeft, 1, 8);
        var textContainer:Sprite = _textSprite(textParticlePixels);
        var textContainerSurface:Sprite = _textSprite(textParticlePixels.clone());
        textContainer.filters = [littleShadow];
        field.addChild(textContainer);
        surface.addChild(textContainerSurface);
        BetweenAS3.serial(
            BetweenAS3.to(textContainerSurface, {alpha:0}, 0.5),
            BetweenAS3.removeFromParent(textContainerSurface),
            BetweenAS3.to(textContainer, {y:465+h*0.5, rotation:Math.random()*120-60}, 2.5, Cubic.easeIn),
            BetweenAS3.removeFromParent(textContainer)
        ).play();
    }
        
    private function _textSprite(bitmapData:BitmapData) : Sprite {
        var container:Sprite = new Sprite();
        var textbmp:Bitmap = new Bitmap(bitmapData);
        textbmp.x = -(container.x = bitmapData.width*0.5);
        textbmp.y = -(container.y = bitmapData.height*0.5);
        container.addChild(textbmp);
        return container;
    }
}


class GesturePad extends Component {
    public var back:Sprite, textField:TextDroppingField, _onMouseUp:Function, _onMouseDown:Function;
    
    private const MOVING_THRESHOLD_PER_FRAME:Number = 2*2;
    private const MOVING_THRESHOLD_PER_GESTURE:Number = 10*10;
    
    private var _prevMouseX:Number, _prevMouseY:Number, _iwidth:Number, _iheight:Number, _infoThres:Number;
    public var head:MouseGestureInfo, current:MouseGestureInfo;
    
    function GesturePad(parent:DisplayObjectContainer, xpos:Number, ypos:Number, size:Number, onMouseDown:Function, onMouseUp:Function) {
        super(parent, xpos, ypos);
        setSize(size, size);
        addChild(back = new Sprite());
        back.addChild(textField = new TextDroppingField(size, size));
        back.addEventListener(MouseEvent.MOUSE_DOWN, onDrag);
        current = head = new MouseGestureInfo();
        _iwidth = 1 / width;
        _iheight = 1 / height;
        _infoThres = MOVING_THRESHOLD_PER_GESTURE * _iheight * _iheight;
        _onMouseUp = onMouseUp;
        _onMouseDown = onMouseDown;
    }
    
    public function flush() : void {
        MouseGestureInfo.freeAll(head);
        current = head.init(0, mouseX*_iwidth, mouseY*_iheight, 0);
    }
    
    protected function onDrag(e:MouseEvent) : void {
        _updatePoint(0);
        _prevMouseX = mouseX;
        _prevMouseY = mouseY;
        _onMouseDown();
        addEventListener(Event.ENTER_FRAME, _capture);
        stage.addEventListener(MouseEvent.MOUSE_UP, onDrop);
    }
    
    protected function onDrop(e:MouseEvent) : void {
        stage.removeEventListener(MouseEvent.MOUSE_UP, onDrop);
        removeEventListener(Event.ENTER_FRAME, _capture);
        _updatePoint(0);
        _onMouseUp();
    }
    
    private function _capture(e:Event) : void {
        var dx:Number, dy:Number, lx:Number, ly:Number;
        dx = mouseX - _prevMouseX, 
        dy = mouseY - _prevMouseY;
        if (dx * dx + dy * dy < MOVING_THRESHOLD_PER_FRAME) return;
        lx = mouseX*_iwidth - current.x;
        ly = mouseY*_iheight - current.y;
        current.dist2 = lx * lx + ly * ly;
        current.frames++;
        var a:Number = (dx==0) ? 10 : (-dy/dx), dir:int = 3;
             if (a > 2.747) dir = 3; // tan(70)
        else if (a > 0.364) dir = 2; // tan(20)
        else if (a >-0.364) dir = 1;
        else if (a >-2.747) dir = 4;
        if ((dir != 1 && dy > 0) || (dir == 1 && dx < 0)) dir += 4;
        if (current.dir != dir) {
            if (current.dir != 0 && current.dist2 < _infoThres) current.init(dir, mouseX*_iwidth, mouseY*_iheight, 0);
            else _updatePoint(dir);
        }
        _prevMouseX = mouseX;
        _prevMouseY = mouseY;
        invalidate();
    }
    
    private function _updatePoint(dir:int) : void {
        current = MouseGestureInfo.alloc(current, dir, mouseX*_iwidth, mouseY*_iheight);
    }
}


class MouseGestureInfo {
    public var dir:int=0, x:Number=0, y:Number=0, dist2:Number=0, frames:int=0, next:MouseGestureInfo=null;
    static private var _freeList:MouseGestureInfo = null;
    
    public function init(dir:int, x:Number, y:Number, frames:int) : MouseGestureInfo {
        this.dir = dir;
        this.x = x;
        this.y = y;
        this.frames = frames;
        this.dist2 = 0;
        return this;
    }
    
    static public function alloc(prev:MouseGestureInfo, dir:int, x:Number, y:Number) : MouseGestureInfo {
        prev.next = _freeList || new MouseGestureInfo();
        _freeList = prev.next.next;
        prev.next.next = null;
        prev.next.init(dir, x, y, 1);
        prev.next.dist2 = 0;
        return prev.next;
    }
    
    static public function freeAll(head:MouseGestureInfo) : void {
        for (var last:MouseGestureInfo = head; last.next != null;)  last = last.next;
        last.next = _freeList;
        _freeList = head.next;
        head.next = null;
    }
}

class GestureAnalyzer {
    static private const REP_COSTS:Vector.<int> = Vector.<int>([0,2,4,6,9,6,4,2, 5]);
    static private const DEL_COSTS:Vector.<int> = Vector.<int>([0,1,2,4,8,4,2,1, 4]);
    static private const INS_COSTS:Vector.<int> = Vector.<int>([0,1,2,4,8,4,2,1, 4]);
    static private const POS_COST:int = 5;
    static private const posFlag:Vector.<int> = Vector.<int>([4,3,2,5,0,1,6,7,8]);
    
    private var dict:* = {};
    
    function GestureAnalyzer() {}
    
    public function map(key:String, gesture:String, flagStartPos:int=0, flagEndPos:int=0) : void {
        dict[key] = new GestureAnalyzerInfo(gesture, flagStartPos, flagEndPos);
    }
    
    public var pattern:Vector.<int> = new Vector.<int>();
    public function analyze(head:MouseGestureInfo, threshold:int = 9999) : Array {
        var mincost:int = threshold, info:MouseGestureInfo, flagStartPos:int, flagEndPos:int,
            cost:int, candidates:Array, dictInfo:GestureAnalyzerInfo;
        pattern.length = 0;
        info = (head.next.dir == 0) ? head.next.next : head.next;
        for (; info.next!=null; info=info.next) pattern.push(info.dir);
        flagStartPos = _posFlag(head.x, head.y);
        flagEndPos   = _posFlag(info.x, info.y);
        for (var key:String in dict) {
            dictInfo = dict[key];
            cost = calcLevenshteinDistance(pattern, dictInfo.pattern);
            cost += (dictInfo.flagStartPos == 0 || dictInfo.flagStartPos == flagStartPos) ? 0 : POS_COST;
            cost += (dictInfo.flagEndPos   == 0 || dictInfo.flagEndPos   == flagEndPos)   ? 0 : POS_COST;
            if (cost < mincost) { 
                mincost = cost; 
                candidates = [key];
            } else if (cost == mincost) {
                candidates.push(key);
            }
        }
        return candidates;
        
        function _posFlag(x:Number, y:Number) : int {
            return posFlag[((x < 0.4) ? 0 : (x < 0.6) ? 1 : 2) + ((y < 0.4) ? 0 : (y < 0.6) ? 3 : 6)];
        }
    }
    
    static private var matrix:Vector.<int> = new Vector.<int>();
    static public function calcLevenshteinDistance(org:Vector.<int>, dst:Vector.<int>) : int {
        var colsize:int = org.length+1, rowsize:int = dst.length+1, matsize:int = colsize * rowsize,
            col:int, row:int, index:int, rep:int, ins:int, del:int, diff:int;
        if (matrix.length < matsize) matrix.length = matsize;
        for (col=0; col<colsize; col++) matrix[col] = col;
        for (row=0, index=0; row<rowsize; row++, index+=colsize) matrix[index] = row;
        colsize--; rowsize--;
        for (col=0; col<colsize; col++) for (row=0; row<rowsize; row++) {
            index = col + row * (colsize+1); // index of [col-1, row-1]
            if (org[col] == 0 || dst[row] == 0) {
                diff = (org[col] == dst[row]) ? 0 : 8;
            } else {
                diff = org[col] - dst[row];
                if (diff < 0) diff = -diff;
            }
            rep = matrix[index] + REP_COSTS[diff];
            index++; // index of [col, row-1]
            del = matrix[index] + DEL_COSTS[diff];
            index += colsize; // index of [col-1, row]
            ins = matrix[index] + INS_COSTS[diff];
            index++; // index of [col, row]
            matrix[index] = (rep < del) ? ((rep < ins) ? rep : ins) : ((del < ins) ? del : ins);
        }
        return matrix[matsize-1];
    }
}


class GestureAnalyzerInfo {
    public var pattern:Vector.<int>, flagStartPos:int, flagEndPos:int;
    function GestureAnalyzerInfo(gesture:String, flagStartPos:int, flagEndPos:int) {
        pattern = new Vector.<int>(gesture.length, true);
        for (var i:int = 0; i<pattern.length; i++) pattern[i] = parseInt(gesture.charAt(i), 16);
        this.flagStartPos = flagStartPos;
        this.flagEndPos = flagEndPos;
    }
}

Forked