forked from: 'Frame by Frame' timer movie for video editing tool

by kikiroom forked from 'Frame by Frame' timer movie for video editing tool (diff: 1)
♥0 | Line 1215 | Modified 2011-03-08 22:54:03 | MIT License
play

ActionScript3 source code

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

// forked from Test_Dept's 'Frame by Frame' timer movie for video editing tool
package {
    
    import flash.display.Graphics;
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.events.TimerEvent;
    import flash.net.FileReference;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.ByteArray;
    import flash.utils.Timer;
    import flash.utils.getTimer;

    /**
     * create 'Frame by Frame' timer movie for video editing tool
     * which DOES NOT support importing runtime animation.
     *
     * @author Test Dept
     */
    [SWF(backgroundColor='#0000ff', width='465', height='465', frameRate=30)]
    public class CreateTimer extends Sprite {

        private var _info : TextField = null;
        private var _delay : Timer = null;
    
        private var _swfName : String = null;
        private var _swfBytes : ByteArray = null;
        
        public function CreateTimer() {

            Wonderfl.capture_delay(10);

            var g : Graphics = graphics;
            g.clear();
            g.beginFill(0x0000ff);
            g.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
            g.endFill();
            
            addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
        }
        
        private function addedToStageHandler(event : Event) : void {

            _info = new TextField();
            _info.defaultTextFormat = new TextFormat(null, 20, 0xffffff);
            _info.multiline = true;
            _info.autoSize = TextFieldAutoSize.LEFT;
            addChild(_info);
            
            _info.text = 'creating movie ...';
            _info.y = 0;
            
            _delay = new Timer(100, 1);
            _delay.addEventListener(TimerEvent.TIMER_COMPLETE, timerCompleteHandler);
            _delay.start();
        }
        
        private function timerCompleteHandler(event : Event) : void {

            _delay.removeEventListener(TimerEvent.TIMER_COMPLETE, timerCompleteHandler);
            
            var lengthInMinutes : int = 5;
            var frameRate : Number = stage.frameRate;
            var textColor : int = 0xffffff;
            var bgColor : int = 0x0000ff;

            var startTime : int = getTimer();
            
            var swf : SWFDocument = createTimer(
                Dsp7Seg,            
                lengthInMinutes,
                frameRate,
                textColor,
                bgColor);
            
            var endTime : int = getTimer();
            
            _swfBytes = new ByteArray();            
            swf.save(_swfBytes);
            
            var scale : Number = stage.stageWidth /
                (swf.getFrameSize().width / 20);
            
            var loader : Loader = new Loader();
            loader.loadBytes(_swfBytes);
            loader.scaleX = scale;
            loader.scaleY = scale;
            addChild(loader);
            
            var infoText : String =
                'click stage to save a movie.\r\r' +
                'length in minutes: ' + lengthInMinutes + ' min\r' +
                'frame rate: ' + frameRate + ' fps\r' +
                'frame count: ' + swf.getFrameCount() + ' (max: 16000)\r' +
                'file size: ' + _swfBytes.length +
                ' bytes\n (after decompression: ' +
                swf.getFileLength() + ' bytes)\r' +
                'creation time: ' + (endTime - startTime) + ' ms\n';


            _info.text = infoText;
            _info.y = stage.stageHeight - _info.textHeight;
            
            _swfName = 'Timer_' + lengthInMinutes +
                'min_' + frameRate + 'fps.swf';
            
            stage.addEventListener(MouseEvent.CLICK, stage_mouseClickHandler);
        }
    
        private function stage_mouseClickHandler(event : Event) : void {
            var ref : FileReference = new FileReference();
            ref.save(_swfBytes, _swfName);
        }
    }
}

/*---------------------------------------------------------
 * some functions fully compatible with 
 * JavaScript in Java Runtime (aka Rhino).
 */

function createTimer(fontClass, lengthInMinutes, frameRate, textColor, bgColor) {

    var frameCount = Math.ceil(frameRate * 60 * lengthInMinutes);
    
    //---------------------------------------------------------------
    
    function formatNumber(num, numDigits) {
        var s = '';
        num = Math.floor(num);
        while (numDigits-- > 0) {
            s = num % 10 + s;
            num = Math.floor(num / 10);
        }
        return s;
    }

    function formatTime(time) {
        time = Math.floor(time);
        var ms = time % 1000;
        time = Math.floor(time / 1000);
        var s = time % 60;
        time = Math.floor(time / 60);
        var m = time % 60;
        return formatNumber(m, 2) +
            ( (ms < 500)? ':' : '\u0020') +
            formatNumber(s, 2) +
            '.' +
            formatNumber(ms / 10, 2);
    }

    //---------------------------------------------------------------
    
    var swf = new SWFDocument();
    swf.setBackgroundColor(bgColor);
    
    var _shapes = {};
    var _chars = '0123456789:.\u0020';
    for (var i = 0; i < _chars.length; i++) {
        var c = _chars.charAt(i);
        _shapes[c] = fontClass.defineCharShape(swf, c, textColor);
        // use local font
        //_shapes[c] = swf.defineTextShape(c, textColor);
    }
    
    function getCharShape(c) {
        return _shapes[c];
    }
    
    var unitShape = getCharShape('0');
    var unitHeight = unitShape.height;
    var xOffset = -unitShape.x;
    var yOffset = -unitShape.y;
    
    var lastText = '';
    var position = {};
    var maxWidth = 0;
    
    for (var i = 0; i < frameCount; i++) {
        
        var text = formatTime(i / frameRate * 1000);
        var length = Math.max(lastText.length, text.length);
        var x = 0;
        
        for (var d = 0; d < length; d++) {
            
            var depth = d + 1; // 1~
            var replace = false;
            var nothing = false;
            
            if (d < lastText.length) {
                
                if (d < text.length) {
                    if (lastText.charAt(d) == text.charAt(d)
                        && x == position[d].x) {
                        // set nothing flag
                        nothing = true;
                    } else {
                        // set replace flag
                        replace = true;
                    }
                } else {
                    // remove last char
                    swf.removeObject2(depth);
                }
            }
            
            if (d < text.length) {
                // place a char
                var c = text.charAt(d);
                var shape = getCharShape(c);
                
                if (!nothing) {
                    swf.placeObject2(depth, shape.shapeId, replace,
                        new Matrix(1, 0, 0, 1, xOffset + x, yOffset) );
                    position[d] = {x : x};
                }
                
                x += (shape.width);
            }
        }
        
        // show frame
        swf.showFrame();
        
        maxWidth = Math.max(maxWidth, x);
        lastText = text;
    }
    
    swf.setFrameRate(frameRate);
    swf.setFrameCount(frameCount);
    swf.setFrameSize(maxWidth, unitHeight);
    
    swf.end();
    
    return swf;
}    

/**
 * 7Seg Character Data
 * 
 * Segment Layout
 *
 *    a
 *   f b
 *    g
 *   e c
 *    d
 *      .
 */

var Dsp7Seg = {
    
    defineCharShape : function(swf, c, color) {
        
        if (c.match(/^[\.\:\u0020]$/) ) {
            return this.defineSymbolShape(swf, c, color);
        }
        
        var pattern = Dsp7Seg[c];
        
        return swf.defineShape(
            new Rectangle(0, 0, Dsp7Seg._SEG_WIDTH, Dsp7Seg._SEG_HEIGHT),
            function(g) {
                Dsp7Seg.drawSegment(g, pattern, color, -1, -1);
            }
        );
    },
    
    defineSymbolShape : function(swf, c, color) {
        
        var pattern = Dsp7Seg[c];
        
        var width = 200;
        
        return swf.defineShape(
            new Rectangle(0, 0, width, Dsp7Seg._SEG_HEIGHT),
            function(g) {
                if (c == '.') {
                    g.beginFill(color);
                    g.drawCircle(width / 2, 840, 46);
                    g.endFill();
                } else if (c == ':') {
                    g.beginFill(color);
                    g.drawCircle(width / 2, 320, 46);
                    g.endFill();
                    g.beginFill(color);
                    g.drawCircle(width / 2, 700, 46);
                    g.endFill();
                }
            }
        );
    },
    
    '0' : 'abcdef',
    '1' : 'bc',
    '2' : 'abdeg',
    '3' : 'abcdg',
    '4' : 'bcfg',
    '5' : 'acdfg',
    '6' : 'acdefg',
    '7' : 'abc',
    '8' : 'abcdefg',
    '9' : 'abcdfg',
    '-' : 'g',
    '.' : '.',
    
    drawSegment : function(
        g, pattern, hiColor, loColor, bgColor
    ) {
        
        if (bgColor >= 0) {
            g.beginFill(bgColor);
            g.drawRect(0, 0, 
                Dsp7Seg._SEG_WIDTH,
                Dsp7Seg._SEG_HEIGHT);
            g.endFill();
        }
        
        var on;
        
        for (var i = 0; i < Dsp7Seg._ALL_SEGMENT.length; i++) {
            var c = Dsp7Seg._ALL_SEGMENT.charAt(i);
            on = (pattern != null && pattern.indexOf(c) != -1);
            Dsp7Seg._drawSegment(g, c, on? hiColor : loColor);
        }
        
        on = (pattern != null && pattern.indexOf('.') != -1);
        Dsp7Seg._drawPoint(g, on? hiColor : loColor);
    },
    
    _drawSegment : function(g, segment, color) {
        
        if (color < 0) {
            return;
        }
        
        var data = Dsp7Seg._SEGMENT_DATA[segment];
        var numPoints = data.length / 2;
        
        g.beginFill(color);
        
        for (var i = 0; i < numPoints; i++) {
            
            var x = data[i * 2];
            var y = data[i * 2 + 1];
            
            if (i == 0) {
                g.moveTo(x, y);
            } else {
                g.lineTo(x, y);
            }
        }
        
        g.endFill();
    },
    
    _drawPoint : function(g, color) {
        if (color < 0) {
            return;
        }
        g.beginFill(color);
        g.drawCircle(542, 840, 46);
        g.endFill();
    },
    
    _SEG_WIDTH : 636,
    _SEG_HEIGHT : 1000,
    _ALL_SEGMENT : 'abcdefg',
    _SEGMENT_DATA : {
        'a' : [575, 138, 494, 211, 249, 211, 194, 137, 213, 120, 559, 120],
        'b' : [595, 160, 544, 452, 493, 500, 459, 456, 500, 220, 582, 146],
        'c' : [525, 560, 476, 842, 465, 852, 401, 792, 441, 562, 491, 516],
        'd' : [457, 860, 421, 892, 94, 892, 69, 864, 144, 801, 394, 801],
        'e' : [181, 560, 141, 789, 61, 856, 48, 841, 96, 566, 148, 516],
        'f' : [241, 218, 200, 453, 150, 500, 115, 454, 166, 162, 185, 145],
        'g' : [485, 507, 433, 555, 190, 555, 156, 509, 204, 464, 451, 464]
    }
};

/*---------------------------------------------------------
 * SWF minimum
 */

import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.ByteArray;
import flash.utils.IDataOutput;

interface ISWFObject {
    function writeSWF(out : SWFOutputStream) : void;
}

interface ISWFSpriteTags extends ISWFObject {
    
    function placeObject2(
        depth : int,
        characterId : int,
        replace : Boolean = false,
        matrix : Matrix = null,
        colorTransform : ColorTransform = null,
        ratio : int = -1,
        name : String = null,
        clipDepth : int    = 0
    ) : void;
    
    function removeObject2(depth : int) : void;
    
    function showFrame() : void;
    
    function end() : void;
}

class SWFOutputStream  {
    
    private var _out : IDataOutput;
    private var _bitBuffer : int;
    private var _bitLength : int;
    
    public function SWFOutputStream(_out : IDataOutput) {
        this._out = _out;
        this._bitBuffer = 0;
        this._bitLength = 0;
    }
    
    public function writeByte(b : int) : void {
        _out.writeByte(b);
    }
    
    public function writeBytes(buffer : ByteArray) : void {
        checkBits();
        _out.writeBytes(buffer);
    }
    
    public function writeMultiByte(text : String, encoding : String) : void {
        checkBits();
        _out.writeMultiByte(text, encoding);
    }
    
    public function flush() : void {
        checkBits();
    }
    
    public function close() : void {
        flush();
    }
    
    public function writeUI8(b : int) : void {
        
        if ( (b & 0xff) != b) {
            throw new Error('not UI8:' + b);
        }
        checkBits();
        
        writeByte(b & 0xff);
    }
    
    public function writeUI16(b : int) : void {
        
        if ( (b & 0xffff) != b) {
            throw new Error('not UI16:' + b);
        }
        checkBits();
        
        writeByte(b & 0xff);
        writeByte( (b >>> 8) & 0xff);
    }
    
    public function writeUI32(b : int) : void {
        
        if ( (b & 0xffffffff) != b) {
            throw new Error('not UI32:' + b);
        }
        checkBits();
        
        writeByte(b & 0xff);
        writeByte( (b >>> 8) & 0xff);
        writeByte( (b >>> 16) & 0xff);
        writeByte( (b >>> 24) & 0xff);
    }
    
    public function writeEncodedInt(i : int) : void {
        
        checkBits();
        
        while (true) {
            
            if ( (i & 0x7f) == i) {
                // end.
                writeByte(i);
                break;
            }
            
            writeByte( (i & 0x7f) | 0x80);
            i >>>= 7;
        }
    }
    
    public function writeString(
        s : String, 
        encoding :  String = 'Utf-8'
    ) : void {
        writeMultiByte(s, encoding);
        writeByte(0);
    }
    
    public function writeUBit(b : Boolean) : void {
        writeUBits(b? 1 : 0, 1);
    }
    
    public function writeUBits(b : int, nbits : int) : void {
        if (b < 0) {
            throw new Error('negative bits:' + b);
        }
        writeBitsImpl(b, nbits);        
    }
    
    public function writeSBits(b : int, nbits : int) : void {
        writeBitsImpl(b, nbits);        
    }
    
    private function writeBitsImpl(b : int, nbits : int) : void {
        
        function trimBits(b : int, nbits : int) : int {
            return (b << (32 - nbits) ) >>> (32 - nbits);
        }
        
        b = trimBits(b, nbits);
        
        while (_bitLength + nbits >= 8) {
            var newBits : int = _bitLength + nbits - 8;
            writeByte( (_bitBuffer << (8 - _bitLength) ) |
                (b >>> newBits) );
            b = trimBits(b, newBits);
            nbits = newBits;
            _bitBuffer = 0;
            _bitLength = 0;
        }
        
        _bitBuffer = (_bitBuffer << nbits) | b;
        _bitLength += nbits;
    }
    
    public function flushBits() : void {
        if (_bitLength > 0) {
            writeByte( (int)(_bitBuffer << (8 - _bitLength) ) );
            _bitBuffer = 0;
            _bitLength = 0;
        }
    }
    
    public function writeObject(obj : ISWFObject) : void {
        obj.writeSWF(this);
    }
    
    private function checkBits() : void {
        if (_bitLength > 0) {
            throw new Error(_bitLength + ' > 0');
        }
    }
    
    public function writeRect(rect : Rectangle) : void {
        
        var xMin : int = rect.x;
        var xMax : int = rect.x + rect.width;
        var yMin : int = rect.y;
        var yMax : int = rect.y + rect.height;
        
        var bits : int = SWFBits.getMaxSBits([
            xMin,
            xMax,
            yMin,
            yMax
        ]);
        
        writeUBits(bits, 5);
        writeSBits(xMin, bits);
        writeSBits(xMax, bits);
        writeSBits(yMin, bits);
        writeSBits(yMax, bits);
        flushBits();
    }
    
    public function writeMatrix(matrix : Matrix) : void {
        
        function toFB(value : Number) : int {
            return Math.round(value * (1 << 16) );
        }
        
        var scaleX : Number = matrix.a;
        var scaleY : Number = matrix.d;
        var rotateSkew0 : Number = matrix.b;
        var rotateSkew1 : Number = matrix.c;
        var translateX : int = matrix.tx;
        var translateY : int = matrix.ty;
        
        var hasScale : Boolean = !(scaleX == 1.0 && scaleY == 1.0);
        var hasRotate : Boolean = !(rotateSkew0 == 0 && rotateSkew1 == 0);
        
        writeUBit(hasScale);
        
        if (hasScale) {
            
            var fbScaleX : int = toFB(scaleX);
            var fbScaleY : int = toFB(scaleY);
            var nScaleBits : int = SWFBits.getMaxSBits([
                fbScaleX,
                fbScaleY
            ]);
            
            writeUBits(nScaleBits, 5);
            writeSBits(fbScaleX, nScaleBits);
            writeSBits(fbScaleY, nScaleBits);
        }
        
        writeUBit(hasRotate);
        
        if (hasRotate) {
            
            var fbRotateSkew0 : int = toFB(rotateSkew0);
            var fbRotateSkew1 : int = toFB(rotateSkew1);
            var nRotateBits : int = SWFBits.getMaxSBits([
                fbRotateSkew0,
                fbRotateSkew1
            ] );
            
            writeUBits(nRotateBits, 5);
            writeSBits(fbRotateSkew0, nRotateBits);
            writeSBits(fbRotateSkew1, nRotateBits);
        }
        
        var nTranslateBits : int = SWFBits.getMaxSBits([
            translateX, translateY]);
        
        writeUBits(nTranslateBits, 5);
        writeSBits(translateX, nTranslateBits);
        writeSBits(translateY, nTranslateBits);
        
        flushBits();
    }
    
    public function writeRGBColor(color : int) : void {
        writeUI8( (color >>> 16) & 0xff);
        writeUI8( (color >>> 8) & 0xff);
        writeUI8(color & 0xff);
    }
    
    public function writeRGBAColor(color : int, alpha : Number) : void {
        writeUI8( (color >>> 16) & 0xff);
        writeUI8( (color >>> 8) & 0xff);
        writeUI8(color & 0xff);
        writeUI8(Math.max(0, Math.min( (alpha * 255), 255) ) );
    }

    public function writeRGBAColorTransform(colorTransform : ColorTransform) : void {
        
        var redMultTerm : int = int(colorTransform.redMultiplier * 256);
        var greenMultTerm : int = int(colorTransform.greenMultiplier * 256);
        var blueMultTerm : int = int(colorTransform.blueMultiplier * 256);
        var alphaMultTerm : int = int(colorTransform.alphaMultiplier * 256);
        var redAddTerm : int = int(colorTransform.redOffset);
        var greenAddTerm : int = int(colorTransform.greenOffset);
        var blueAddTerm : int = int(colorTransform.blueOffset);
        var alphaAddTerm : int = int(colorTransform.alphaOffset);
        
        var hasAddTerms : Boolean = !(
            redAddTerm == 0 &&
            greenAddTerm == 0 &&
            blueAddTerm == 0 &&
            alphaAddTerm == 0);
        
        var hasMultTerms : Boolean = !(
            redMultTerm == 256 &&
            greenMultTerm == 256 &&
            blueMultTerm == 256 &&
            alphaMultTerm == 256);
        
        var nBits : int = SWFBits.getMaxSBits([
            redAddTerm,
            greenAddTerm,
            blueAddTerm,
            alphaAddTerm,
            redMultTerm,
            greenMultTerm,
            blueMultTerm,
            alphaMultTerm
        ]);
        
        writeUBit(hasAddTerms);
        writeUBit(hasMultTerms);
        
        writeUBits(nBits, 4);
        
        if (hasMultTerms) {
            writeSBits(redMultTerm, nBits);
            writeSBits(greenMultTerm, nBits);
            writeSBits(blueMultTerm, nBits);
            writeSBits(alphaMultTerm, nBits);
        }
        
        if (hasAddTerms) {
            writeSBits(redAddTerm, nBits);
            writeSBits(greenAddTerm, nBits);
            writeSBits(blueAddTerm, nBits);
            writeSBits(alphaAddTerm, nBits);
        }
        
        flushBits();
    }
}

class SWFBits {
    
    public function SWFBits() {
        throw new Error();    
    }
    
    public static function getMaxUBits(bits : Array) : int {
        if (bits.length == 0) {
            throw new Error('empty bits');
        }
        var maxBits : int= 0;
        for (var i : int = 0; i < bits.length; i++) {
            maxBits = Math.max(maxBits, getUBits(bits[i]) );
        }
        return maxBits;
    }
    
    public static function getMaxSBits(bits : Array) : int {
        if (bits.length == 0) {
            throw new Error('empty bits');
        }
        var maxBits : int = 0;
        for (var i : int = 0; i < bits.length; i++) {
            maxBits = Math.max(maxBits, getSBits(bits[i]) );
        }
        return maxBits;
    }
    
    public static function getUBits(bits : int) : int {
        if (bits < 0) {
            throw new Error('negative bits:' + bits);
        }
        for (var i : int = 31; i >= 0; i--) {
            if ( ( (bits >>> i) & 1) != 0) {
                return i + 1;
            }
        }
        return 1;
    }
    
    public static function getSBits(bits : int) : int {
        var sign : int = (bits >>> 31);
        
        for (var i : int = 31 - 1; i >= 0; i--) {
            if (sign != ( (bits >>> i) & 1) ) {
                return i + 2;
            }
        }
        return 2;
    }
}

class SWFShapeRecord implements ISWFObject {
    
    private var _typeFlag : Boolean;
    
    public function SWFShapeRecord(typeFlag : Boolean) {
        _typeFlag = typeFlag;
    }
    
    public function writeSWF(out : SWFOutputStream) : void {
        out.writeUBit(_typeFlag);
    }
}

class SWFLineStyle implements ISWFObject {
    
    private var _width : int;
    private var _color : int;
    private var _alpha : Number;
    
    public function SWFLineStyle(width : int, color : int, alpha : Number) {
        _width = width;
        _color = color;
        _alpha = alpha;
    }
    
    public function writeSWF(out : SWFOutputStream) : void {
        out.writeUI16(_width);
        out.writeRGBAColor(_color, _alpha);
    }
}

class SWFFillStyle implements ISWFObject {
    
    public function SWFFillStyle() {
    }
    
    public function writeSWF(out : SWFOutputStream) : void {
    }
}

class SWFSolidFill extends SWFFillStyle {
    
    private var color : int;
    private var alpha : Number;
    
    public function SWFSolidFill(color : int, alpha : Number) {
        this.color = color;
        this.alpha = alpha;
    }
    
    override public function writeSWF(out : SWFOutputStream) : void {
        out.writeUI8(0);
        out.writeRGBAColor(color, alpha);
    }
}

class SWFStyleChangeRecord extends SWFShapeRecord {
    
    private var _fillStyle0 : int;
    private var _fillStyle1 : int;
    private var _lineStyle : int;
    private var _stateMoveTo : Boolean;
    private var _moveDeltaX : int;
    private var _moveDeltaY : int;
    
    public var numFillBits : int;
    public var numLineBits : int;
    
    public function SWFStyleChangeRecord(
        fillStyle0 : int,
        fillStyle1 : int,
        lineStyle : int,
        stateMoveTo : Boolean,
        moveDeltaX : int,
        moveDeltaY : int
    ) {
        
        super(false);
        
        _fillStyle0 = fillStyle0;
        _fillStyle1 = fillStyle1;
        _lineStyle = lineStyle;
        _stateMoveTo = stateMoveTo;
        _moveDeltaX = moveDeltaX;
        _moveDeltaY = moveDeltaY;
    }
    
    override public function writeSWF(out : SWFOutputStream) : void {
        
        super.writeSWF(out);
        
        var stateNewStyles : Boolean = false;
        var stateLineStyle : Boolean = (_lineStyle >= 0);
        var stateFillStyle1 : Boolean = (_fillStyle1 >= 0);
        var stateFillStyle0 : Boolean = (_fillStyle0 >= 0);
        
        out.writeUBit(stateNewStyles);
        out.writeUBit(stateLineStyle);
        out.writeUBit(stateFillStyle1);
        out.writeUBit(stateFillStyle0);
        out.writeUBit(_stateMoveTo);
        
        if (_stateMoveTo) {
            var moveBits : int = SWFBits.getMaxSBits([
                _moveDeltaX,
                _moveDeltaY
            ]);
            out.writeUBits(moveBits, 5);
            out.writeSBits(_moveDeltaX, moveBits);
            out.writeSBits(_moveDeltaY, moveBits);
        }
        
        if (stateFillStyle0) {
            out.writeUBits(_fillStyle0, numFillBits);
        }
        
        if (stateFillStyle1) {
            out.writeUBits(_fillStyle1, numFillBits);
        }
        
        if (stateLineStyle) {
            out.writeUBits(_lineStyle, numLineBits);
        }
        
        if (stateNewStyles) {
            
        }
    }
}

class SWFStraightEdgeRecord extends SWFShapeRecord {
    
    private var _deltaX : int;
    private var _deltaY : int;
    
    public function SWFStraightEdgeRecord(deltaX : int, deltaY : int) {
        super(true);
        _deltaX = deltaX;
        _deltaY = deltaY;
    }
    
    override public function writeSWF(out : SWFOutputStream) : void {
        
        super.writeSWF(out);
        
        var generalLineFlag : Boolean = (_deltaX != 0 && _deltaY != 0);
        var vertLineFlag : Boolean = (_deltaX == 0);
        
        var numBits : int = SWFBits.getMaxSBits([
            _deltaX,
            _deltaY
        ]);
        
        out.writeUBit(true);
        out.writeUBits(numBits - 2, 4);
        
        out.writeUBit(generalLineFlag);
        
        if (generalLineFlag) {
            
            out.writeSBits(_deltaX, numBits);
            out.writeSBits(_deltaY, numBits);
            
        } else {
            
            out.writeUBit(vertLineFlag);
            
            if (!vertLineFlag) {
                out.writeSBits(_deltaX, numBits);
            } else {
                out.writeSBits(_deltaY, numBits);
            }
        }
    }
}

class SWFCurvedEdgeRecord extends SWFShapeRecord {
    
    private var _controlDeltaX : int;
    private var _controlDeltaY : int;
    private var _anchorDeltaX : int;
    private var _anchorDeltaY : int;
    
    public function SWFCurvedEdgeRecord(
        controlDeltaX : int,
        controlDeltaY : int, 
        anchorDeltaX : int,
        anchorDeltaY : int
    ) {
        
        super(true);
        
        _controlDeltaX = controlDeltaX;
        _controlDeltaY = controlDeltaY;
        _anchorDeltaX = anchorDeltaX;
        _anchorDeltaY = anchorDeltaY;
    }
    
    override public function writeSWF(out : SWFOutputStream) : void {
        
        super.writeSWF(out);
        
        var numBits : int = SWFBits.getMaxSBits([
            _controlDeltaX,
            _controlDeltaY,
            _anchorDeltaX,
            _anchorDeltaY
        ]);
        
        out.writeUBit(false);
        out.writeUBits(numBits - 2, 4);
        
        out.writeSBits(_controlDeltaX, numBits);
        out.writeSBits(_controlDeltaY, numBits);
        out.writeSBits(_anchorDeltaX, numBits);
        out.writeSBits(_anchorDeltaY, numBits);
    }
}

class SWFEndShapeRecord extends SWFShapeRecord {
    
    public function SWFEndShapeRecord() {
        super(false);
    }
    
    override public function writeSWF(out : SWFOutputStream) : void {
        super.writeSWF(out);
        out.writeUBits(0, 5);
    }
}

class SWFShapeWithStyle implements ISWFObject {
    
    private var _fillStyles : Array;
    private var _lineStyles : Array;
    private var _records : Array;
    
    public function SWFShapeWithStyle() {
        _fillStyles = new Array();
        _lineStyles = new Array();
        _records = new Array();
    }
    
    public function solidFill(color : int, alpha : Number) : int {
        return addFillStyle(new SWFSolidFill(color, alpha) );
    }
    
    public function lineStyle(width : int, color : int, alpha : Number) : int {
        return addLineStyle(new SWFLineStyle(width, color, alpha) );
    }
    
    public function styleChangeRecord(
        fillStyle0 : int,
        fillStyle1 : int,
        lineStyle : int,
        stateMoveTo : Boolean,
        moveDeltaX : int,
        moveDeltaY : int
    ) : void {
        _records.push(new SWFStyleChangeRecord(fillStyle0, fillStyle1, lineStyle, stateMoveTo, moveDeltaX, moveDeltaY) );
    }
    
    public function straightEdgeRecord(deltaX : Number, deltaY : Number) : void {
        _records.push(new SWFStraightEdgeRecord(deltaX, deltaY) );
    }
    
    public function curvedEdgeRecord(
        controlDeltaX : int, controlDeltaY : int, 
        anchorDeltaX : int, anchorDeltaY : int
    ) : void {
        _records.push(new SWFCurvedEdgeRecord(controlDeltaX, controlDeltaY, anchorDeltaX, anchorDeltaY) );
    }
    
    public function endShapeRecord() : void {
        _records.push(new SWFEndShapeRecord() );
    }
    
    protected function addFillStyle(fillStyle : SWFFillStyle) : int {
        _fillStyles.push(fillStyle);
        return _fillStyles.length;
    }
    
    protected function addLineStyle(lineStyle : SWFLineStyle) : int {
        _lineStyles.push(lineStyle);
        return _lineStyles.length;
    }
    
    public function writeSWF(out : SWFOutputStream) : void {
        
        var i : int;
        
        writeCount(out, _fillStyles.length);
        for (i = 0; i < _fillStyles.length; i++) {
            out.writeObject(_fillStyles[i]);
        }
        
        writeCount(out, _lineStyles.length);
        for (i = 0; i < _lineStyles.length; i++) {
            out.writeObject(_lineStyles[i]);
        }
        
        var numFillBits : int = SWFBits.getUBits(_fillStyles.length);
        var numLineBits : int = SWFBits.getUBits(_lineStyles.length);
        
        out.writeUBits(numFillBits, 4);
        out.writeUBits(numLineBits, 4);
        out.flushBits(); // nothing.
        
        for (i = 0; i < _records.length; i++) {
            var record : SWFShapeRecord = _records[i];
            if (record is SWFStyleChangeRecord) {
                SWFStyleChangeRecord(record).numFillBits = numFillBits;
                SWFStyleChangeRecord(record).numLineBits = numLineBits;
            }
            out.writeObject(record);
        }
        out.flushBits();
    }
    
    private function writeCount(out : SWFOutputStream, count : int) : void {
        if (count < 255) {
            out.writeUI8(count);
        } else {
            out.writeUI8(0xff);
            out.writeUI16(count);
        }
    }
}

class SWFTag implements ISWFObject {
    
    private var _tagType : int;
    private var _writeRecord : Function;
    
    public function SWFTag(tagType : int, writeRecord : Function = null) {
        _tagType = tagType;
        _writeRecord = writeRecord;
    }
    
    public function writeSWF(out : SWFOutputStream) : void {
        
        var b : ByteArray = new ByteArray();
        var fout : SWFOutputStream = new SWFOutputStream(b);
        
        try {
            if (_writeRecord != null) {
                _writeRecord(fout);
            }
        } finally {
            fout.close();
        }
        
        if (b.length > 62) {
            out.writeUI16( (_tagType << 6) | 0x3f);
            out.writeUI32(b.length);
        } else {
            out.writeUI16( (_tagType << 6) | b.length);
        }
        
        out.writeBytes(b);
    }
}

class SWFTags implements ISWFObject, ISWFSpriteTags {
    
    private static const END : int = 0;
    private static const SHOW_FRAME : int = 1;
    private static const SET_BACKGROUND_COLOR : int = 9;
    private static const PLACE_OBJECT2 : int = 26;
    private static const REMOVE_OBJECT2 : int = 28;
    private static const DEFINE_SHAPE3 : int = 32;
    private static const DEFINE_SPRITE : int = 39;
    
    private var _bout : ByteArray;
    private var _fout : SWFOutputStream;
    private var _closed : Boolean;
    private var _characterId : int;
    
    public function SWFTags() {
        _bout = new ByteArray();
        _fout = new SWFOutputStream(_bout);
        _closed = false;
        _characterId = 0;
    }
    
    public function writeObject(tag : SWFTag) : void {
        if (_closed) {
            throw new Error('stream closed.');
        }
        _fout.writeObject(tag);
    }
    
    public function writeSWF(out : SWFOutputStream) : void {
        if (!_closed) {
            _fout.close();
            _closed = true;
        }
        out.writeBytes(_bout);
    }
    
    private function nextCharacterId() : int {
        _characterId++;
        return _characterId;
    }
    
    public function end() : void {
        writeObject(new SWFTag(END) );
    }
    
    public function showFrame() : void {
        writeObject(new SWFTag(SHOW_FRAME) );
    }
    
    public function setBackgroundColor(color : int) : void {
        writeObject(new SWFTag(SET_BACKGROUND_COLOR,
            function(out : SWFOutputStream) : void {
                out.writeRGBColor(color);
            }            
        ) );
    }
    
    public function defineShape3(
        shapeBounds : Rectangle,
        shapeWithStyle : SWFShapeWithStyle
    ) : int {
        var  characterId : int = nextCharacterId();
        writeObject(new SWFTag(DEFINE_SHAPE3,
            function(out : SWFOutputStream) : void {
                out.writeUI16(characterId); 
                out.writeRect(shapeBounds);
                out.writeObject(shapeWithStyle);
            }            
        ) );
        return characterId;
    }
    
    public function defineSprite(frameCount : int, controlTags : ISWFSpriteTags) : int {
        var  characterId : int = nextCharacterId();
        writeObject(new SWFTag(DEFINE_SPRITE,
            function(out : SWFOutputStream) : void {
                out.writeUI16(characterId); 
                out.writeUI16(frameCount);
                out.writeObject(controlTags);
            }
        ) );
        return characterId;
    }
    
    public function placeObject2(
        depth : int,
        characterId : int,
        replace : Boolean = false,
        matrix : Matrix = null,
        colorTransform : ColorTransform = null,
        ratio : int = -1,
        name : String = null,
        clipDepth : int    = 0
    ) : void {
        writeObject(new SWFTag(PLACE_OBJECT2,
            function(out : SWFOutputStream) : void {
                var placeFlagHasClipActions : Boolean = false;
                var placeFlagHasClipDepth : Boolean = (clipDepth != 0);
                var placeFlagHasName : Boolean = (name != null);
                var placeFlagHasRatio : Boolean = (ratio >= 0);
                var placeFlagHasColorTransform : Boolean = (colorTransform != null);
                var placeFlagHasMatrix : Boolean = (matrix != null);
                var placeFlagHasCharacter : Boolean = (characterId != 0);
                var placeFlagMove : Boolean = replace || !placeFlagHasCharacter;
                
                out.writeUBit(placeFlagHasClipActions);
                out.writeUBit(placeFlagHasClipDepth);
                out.writeUBit(placeFlagHasName);
                out.writeUBit(placeFlagHasRatio);
                out.writeUBit(placeFlagHasColorTransform);
                out.writeUBit(placeFlagHasMatrix);
                out.writeUBit(placeFlagHasCharacter);
                out.writeUBit(placeFlagMove);
                out.flushBits(); // nothing.
                
                out.writeUI16(depth);
                
                if (placeFlagHasCharacter) {
                    out.writeUI16(characterId);
                }
                
                if (placeFlagHasMatrix) {
                    out.writeMatrix(matrix);
                }
                
                if (placeFlagHasColorTransform) {
                    out.writeRGBAColorTransform(colorTransform);
                }
                
                if (placeFlagHasRatio) {
                    out.writeUI16(ratio);
                }
                
                if (placeFlagHasName) {
                    out.writeString(name);
                }
                
                if (placeFlagHasClipDepth) {
                    out.writeUI16(clipDepth);
                }
            }            
        ) );
    }
    
    public function removeObject2(depth : int) : void {
        writeObject(new SWFTag(REMOVE_OBJECT2,
            function(out : SWFOutputStream) : void {
                out.writeUI16(depth);
            }            
        ) );
    }
}

class SWFDocument extends SWFTags {
    
    public static const SIGNATURE_SWF : String = 'FWS';
    public static const SIGNATURE_SWC : String = 'CWS';
    
    private var _signature : String = SIGNATURE_SWC;
    private var _version : int = 9;
    private var _fileLength : int = 0;
    private var _frameSize : Rectangle = new Rectangle(0, 0, 11000, 8000);
    private var _frameRate : Number = 24;
    private var _frameCount : int = 1;

    public function SWFDocument() {
    }

    public function defineShape(shapeBounds : Rectangle, shapeWithStyle : *) : * {

        if (shapeWithStyle is Function) {
            var handler : Function = shapeWithStyle as Function;
            shapeWithStyle = new SWFShapeWithStyle();
            var g : SWFGraphics = new SWFGraphics(shapeWithStyle);
            handler(g);
            shapeWithStyle.endShapeRecord();
            if (shapeBounds == null) {
                shapeBounds = g.getBounds();
            }
        }
        
        var shapeId : int = defineShape3(shapeBounds, shapeWithStyle);
        return {
            shapeId : shapeId,
            x : shapeBounds.x,
            y : shapeBounds.y,
            width : shapeBounds.width,
            height : shapeBounds.height
        };
    }
    
    public function setSignature(signature : String) : void {
        _signature = signature;
    }
    public function getSignature() : String {
        return _signature;
    }

    public function setVersion(version : int) : void {
        _version = version;
    }
    public function getVersion() : int {
        return _version;
    }

    private function setFileLength(fileLength : int) : void {
        _fileLength = fileLength;
    }
    public function getFileLength() : int {
        return _fileLength;
    }

    public function setFrameRate(frameRate : Number) : void {
        _frameRate = frameRate;
    }
    public function getFrameRate() : Number {
        return _frameRate;
    }
    
    public function setFrameCount(frameCount : int) : void {
        _frameCount = frameCount;
    }
    public function getFrameCount() : int {
        return _frameCount;
    }
    
    public function setFrameSize(width : int, height : int) : void {
        _frameSize = new Rectangle(0, 0, width, height);
    }
    public function getFrameSize() : Rectangle {
        return _frameSize;
    }
    
    public function save(out : IDataOutput) : void {
    
        validateSpec();
        
        var contents : ByteArray = getContents();
        
        // fileLength: sizeof(UI8 * 4 + UI32) + contents.length
        setFileLength(8 + contents.length);
        
        var sout : SWFOutputStream = new SWFOutputStream(out);
        
        //----------------
        // writeBytes header
        
        var signature : String = getSignature();
        for (var i : int = 0; i < signature.length; i++) {
            sout.writeUI8(signature.charCodeAt(i) );
        }
        sout.writeUI8(_version);
        sout.writeUI32(_fileLength);
        
        //----------------
        // writeBytes body
        
        if (signature == SIGNATURE_SWC) {
            contents.compress();
        }
        
        sout.writeBytes(contents);
        
        sout.flush();
    }
    
    private function validateSpec() : void {
        var maxFrameCount : int = 16000;
        if (getFrameCount() > maxFrameCount) {
            throw new Error('frameCount over:' +
                getFrameCount() + ' > ' + maxFrameCount);
        }
        if (getFrameSize().x != 0 ||
            getFrameSize().y != 0 ||
            getFrameSize().width < 1 ||
            getFrameSize().height < 1) {
            throw new Error('bad frameSize:' + getFrameSize() );
        }
    }
    
    private function getContents() : ByteArray {
        
        var bout : ByteArray = new ByteArray();
        var fout : SWFOutputStream = new SWFOutputStream(bout);
        
        try {
            
            // stage bounds.
            fout.writeRect(getFrameSize() );
            
            // frame rate (FIXED8).
            var rate : int = int(getFrameRate() * 256);
            fout.writeUI8(rate % 256);
            fout.writeUI8(rate / 256);
            
            // frame count.
            fout.writeUI16(getFrameCount() );
            
            // tags
            fout.writeObject(this);
            
        } finally {
            fout.close();
        }
        return bout;
    }
}

class SWFGraphics {
    
    private var _target : SWFShapeWithStyle;
    private var _bounds : Rectangle;
    
    private var _startX : int;
    private var _startY : int;
    private var _lastX : int;
    private var _lastY : int;
    
    public function SWFGraphics(target : SWFShapeWithStyle) {
        
        _target = target;
        _bounds = new Rectangle();
        
        _startX = 0;
        _startY = 0;
        _lastX = 0;
        _lastY = 0;
    }
    
    public function getBounds() : Rectangle {
        return _bounds;
    }
    
    public function lineStyle(thickness : int = 0, color : int = 0x000000, alpha : Number = 1.0) : void {
        var lineStyle : int = (thickness > 0) ? _target.lineStyle(thickness, color, alpha) : 0;
        _target.styleChangeRecord(-1, -1, lineStyle, false, 0, 0);
    }
    
    public function beginFill(color : int, alpha : Number = 1.0) : void {
        var fillStyle : int = _target.solidFill(color, alpha);
        _target.styleChangeRecord(-1, fillStyle, -1, false, 0, 0);
    }
    
    public function endFill() : void {
        
        if (_lastX != _startX || _lastY != _startY) {
            // force close.
            lineTo(_startX, _startY);
        }
        
        _target.styleChangeRecord(-1, 0, -1, false, 0, 0);
    }
    
    public function moveTo(x : int, y : int) : void {
        
        _target.styleChangeRecord(-1, -1, -1, true, x, y);
        
        _lastX = x;
        _lastY = y;
        
        _startX = x;
        _startY = y;
        
        addToBounds(x, y);
    }
    
    public function lineTo(x : int, y : int) : void {
        
        var dx : int = x - _lastX;
        var dy : int = y - _lastY;
        
        _target.straightEdgeRecord(dx, dy);
        
        _lastX = x;
        _lastY = y;
        
        addToBounds(x, y);
    }
    
    public function curveTo(controlX : int, controlY : int, anchorX : int, anchorY : int) : void {
        
        var cx : int = controlX - _lastX;
        var cy : int = controlY - _lastY;
        var ax : int = anchorX - controlX;
        var ay : int = anchorY - controlY;
        
        _target.curvedEdgeRecord(cx, cy, ax, ay);
        
        _lastX = anchorX;
        _lastY = anchorY;
        
        addToBounds(controlX, controlY);
        addToBounds(anchorX, anchorY);
    }
    
    private function addToBounds(x : int, y : int) : void {
        var bounds : Rectangle = getBounds();
        var xMin : int = bounds.x;
        var xMax : int = bounds.x + bounds.width;
        var yMin : int = bounds.y;
        var yMax : int = bounds.y + bounds.height;
        xMin = Math.min(x, xMin);
        xMax = Math.max(x, xMax);
        yMin = Math.min(y, yMin);
        yMax = Math.max(y, yMax);
        bounds.x = xMin;
        bounds.width = xMax - xMin;
        bounds.y = yMin;
        bounds.height = yMax - yMin;
    }
    
    public function drawRect(x : int, y : int, width : int, height : int) : void {
        moveTo(x, y);
        lineTo(x + width, y);
        lineTo(x + width, y + height);
        lineTo(x, y + height);
        lineTo(x, y);
    }
    
    public function drawCircle(x : int, y : int, r : int) : void {
        drawPie(x, y, r, 0, Math.PI * 2, false);
    }
    
    private function drawPie(x : int, y : int, r : int, t1 : Number, t2 : Number, cont : Boolean) : void {
        
        var ra : Number = Math.PI / 2;
        
        var div : int = Math.max(1, (int)(Math.abs(t1 - t2) / 0.4) );
        
        var lx : Number = 0;
        var ly : Number = 0;
        var lt : Number = 0;
        
        for (var i : int = 0; i <= div; i++) {
            
            var ct : Number = t1 + (t2 - t1) * i / div;
            var cx : Number = Math.cos(ct) * r + x;    
            var cy : Number = Math.sin(ct) * r + y;    
            
            if (i == 0) {
                if (!cont) {
                    moveTo(cx, cy);
                }
            } else {
                var cp : Point = getPieControlPoint(
                    lx, ly, lt + ra, cx, cy, ct + ra); 
                curveTo(cp.x, cp.y, cx, cy);            
            }
            
            lx = cx;
            ly = cy;
            lt = ct;
        }
    }
    
    private function getPieControlPoint(x1 : Number, y1 : Number, t1 : Number, x2 : Number, y2 : Number, t2 : Number) : Point {
        
        var x12 : Number = x2 - x1;
        var y12 : Number = y2 - y1;
        
        var l12 : Number = Math.sqrt(x12 * x12 + y12 * y12);
        var t12 : Number = Math.atan2(y12, x12);
        
        var l13 : Number = l12 * Math.sin(t2 - t12) / Math.sin(t2 - t1);
        
        var x3 : Number = x1 + l13 * Math.cos(t1);
        var y3 : Number = y1 + l13 * Math.sin(t1);
        
        return new Point(x3,  y3);
    }
}