package { import flash.display.*; import flash.events.Event; import flash.events.MouseEvent; import flash.net.URLLoader; import flash.net.URLRequest; [SWF(width=465, height=465, frameRate=12, backgroundColor=0xFFFFFF)] public class Tree extends MovieClip { public function Tree(){ var cfdg :String = <>; start( cfdg ); } private function start( cfdg :String ) :void { var art :ContextFreeArt = new ContextFreeArt( cfdg, 465, 465 ); addChild( art ); stage.addEventListener( MouseEvent.CLICK, function(ev:MouseEvent) :void { art.tick(); }); } } } import flash.display.Sprite; class ContextFreeArt extends Sprite { private var renderer :Renderer; public function ContextFreeArt( cfdg_text :String, width :Number = 640, height :Number = 480 ) { var t :Tokenizer = new Tokenizer; var tokens :Array = t.tokenize( cfdg_text ); var c :Compiler = new Compiler; var compiled :Object = c.compile( tokens ); renderer = new Renderer( width, height ); renderer.clearQueue(); renderer.render( compiled, this ); } public function tick() :void { renderer.tick(); } } class Compiler{ private const keywords :Array = [ "startshape", "rule", "background" ]; public var compiled :Object = {}; public var state :IState; private var curKey :String; private var curValues :Array; private var obj :Object; public function Compiler(){} public function compile( tokens :Array ) :Object { state = new General; while ( tokens.length > 0 ) { var token :String = tokens.shift(); var nextState :Array = state.eat( token, this ); if ( nextState ) { next( nextState ); } } return compiled; } private function next( state_and_args :Array ) :void { var className :String = state_and_args.shift(); // uppercase the 1st char className = className.substr(0,1).toUpperCase() + className.substr(1); switch( className ) { case "Startshape": state = new Startshape; break; case "General": state = new General; break; case "Background": state = new Background; break; case "Rule": state = new Rule; break; case "RuleWeight": state = new RuleWeight( state_and_args ); break; case "RuleDraw": state = new RuleDraw( state_and_args ); break; case "ShapeAdjustment": state = new ShapeAdjustment( state_and_args ); break; default: throw('unknown className: '+className); } } } class Tokenizer{ private var input :String; private const stopChars :Array = [" ", "{", "}", "\n", "\r", "\t"]; public function Tokenizer() {} public function tokenize( _input :String ) :Array { input = _input; // To make it easier to parse, we pad the brackets with spaces. input = input.replace( /([{}])/g, " $1"); var tokens :Array = new Array; var head :Object = { lastPos: 0 }; while( 1 ) { head = tokenizeNext( head.lastPos ); if ( head == null ) { break; } if ( head.token ) tokens.push( head.token ); } return tokens; } private function tokenizeNext( pos :Number ) :Object { var stops :Array = new Array; var len :int = stopChars.length; for ( var i:int=0; i 0 ) { isRendering = true; var concurrent :int = Math.min( queue.length - 1, maxThreads ); for ( var i :int=0; i <= concurrent; i++ ) { var args :Array = queue.shift(); drawRule.apply( null, args ); } center(); } } private function center() :void { var rect :Rectangle = container.getRect( container ); // resize centeringScale = Math.min( width / rect.width, height / rect.height ) * 0.9; centeringMatrix.a = centeringMatrix.d = centeringScale; // centering centeringMatrix.tx = width /2 - (rect.left + rect.right ) / 2 * centeringScale; centeringMatrix.ty = height/2 - (rect.top + rect.bottom) / 2 * centeringScale; container.transform.matrix = centeringMatrix; } private function draw() :void { var ruleName :String = compiled.startshape; var foregroundColor :Color = new Color; drawRule( ruleName, new Matrix, foregroundColor ); } private function drawRule( ruleName :String, mtx :Matrix, color :Color, priority :Number = 0 ) :void { // When things get too small, we can stop rendering. // Too small, in this case, means less than half a pixel. if( Math.abs( mtx.a ) * globalScale * centeringScale < 0.5 && Math.abs( mtx.b ) * globalScale * centeringScale < 0.5 ) return; var shape :Object = chooseShape( ruleName ); drawShape( shape, mtx, color, priority ); } private function chooseShape( ruleName :String ) :Object { // Choose which rule to go with... var choices :Array = compiled[ ruleName ]; if ( ! choices ) { throw("no rule found for "+ruleName); } var sum :Number = 0; for( var i :int=0; i 360){ h -= 360; } var r :Number, g :Number, b :Number; if (h < 120){ r = (120 - h) / 60; g = h / 60; b = 0; }else if (h < 240){ r = 0; g = (240 - h) / 60; b = (h - 120) / 60; }else{ r = (h - 240) / 60; g = 0; b = (360 - h) / 60; } r = Math.min(r, 1); g = Math.min(g, 1); b = Math.min(b, 1); r = 2 * s * r + (1 - s); g = 2 * s * g + (1 - s); b = 2 * s * b + (1 - s); if (l < 0.5){ r = l * r; g = l * g; b = l * b; }else{ r = (1 - l) * r + 2 * l - 1; g = (1 - l) * g + 2 * l - 1; b = (1 - l) * b + 2 * l - 1; } r = Math.ceil(r * 255); g = Math.ceil(g * 255); b = Math.ceil(b * 255); // Putting a semicolon at the end of an rgba definition // causes it to not work. //return "rgba(" + r + ", " + g + ", " + b + ", " + a + ")"; // , to do: graphics.beginFill.apply( null, color.split(',') ) return [ (r * 256*256 + g * 256 + b), a ]; } // hsba to hsba private function adjustColor( adjs :Adjustment, color :Color ) :Color { // See http://www.contextfreeart.org/mediawiki/index.php/Shape_adjustments var newColor :Color = new Color; newColor.h = color.h; newColor.s = color.s; newColor.b = color.b; newColor.a = color.a; // Add num to the drawing hue value, modulo 360 newColor.h += adjs.hue; newColor.h %= 360; // If adj<0 then change the drawing [blah] adj% toward 0. // If adj>0 then change the drawing [blah] adj% toward 1. if ( adjs.saturation != 0 ) { if( adjs.saturation > 0 ){ newColor.s += adjs.saturation * (1-color.s); } else { newColor.s += adjs.saturation * color.s; } } if ( adjs.brightness != 0 ) { if( adjs.brightness > 0 ){ newColor.b += adjs.brightness * (1-color.b); } else { newColor.b += adjs.brightness * color.b; } } if ( adjs.alpha != 0 ) { if( adjs.alpha > 0 ){ newColor.a += adjs.alpha * (1-color.a); } else { newColor.a += adjs.alpha * color.a; } } return newColor; } public function clearQueue() :void { queue = new Array; } } class Color { public var h :Number = 0; public var s :Number = 0; public var b :Number = 0; public var a :Number = 1; public function Color(){ } } class Adjustment{ public var name :String; public var flipDefined :Boolean = false; public var flip :Number; public var sizeX :Number = 1; public var sizeY :Number = 1; public var rotate :Number = 0; public var x :Number = 0; public var y :Number = 0; public var hue :Number = 0; public var saturation :Number = 0; public var brightness :Number = 0; public var alpha :Number = 0; public function Adjustment() { } public function fill( obj :Object ) :void { for( var key :String in obj ){ switch( key ) { case "f": case "flip": flipDefined = true; flip = obj[key]; break; case "s": case "size": var size :* = obj[key]; if ( typeof(size) == "number" ) { size = [size,size]; } sizeX = size[0]; sizeY = size[1]; break; case "r": rotate = obj[key]; break; case "h": hue = obj[key]; break; case "sat": saturation = obj[key]; break; case "b": brightness = obj[key]; break; case "a": alpha = obj[key]; break; case "rotate": case "x": case "y": case "hue": case "saturation": case "brightness": case "alpha": this[key] = obj[key]; break; default: throw("unsupported adjustment: "+key); } } } } class Rule implements IState { public function Rule(){ } public function eat( token :String, compiler :Compiler ) :Array { var ruleName :String = token; // Create a blank rule if it doesn't aleady exist if ( ! compiler.compiled[ ruleName ] ) { compiler.compiled[ ruleName ] = []; } return [ "ruleWeight", ruleName ]; } } class RuleDraw implements IState { private var weight :Number = 1; private var ruleName :String; public function RuleDraw( args :Array ) { ruleName = args[0]; } public function eat( token :String, compiler :Compiler ) :Array { if( token == "}" ){ return [ "general" ]; } return [ "shapeAdjustment", token, ruleName ]; } } class Startshape implements IState { public function Startshape() {} public function eat( token :String, compiler :Compiler ) :Array { // uppercase the 1st char compiler.compiled[ "startshape" ] = token; return [ "general" ]; } } class AbstractArgument implements IState { protected var curKey :String = null; protected var curValues :Array = []; protected var obj :Object = {}; protected var compiler :Compiler; public function AbstractArgument(){} public function eat( token :String, _compiler :Compiler ) :Array { compiler = _compiler; switch ( token ) { case "}": flushKey(); return onDone( obj ); case "{": return null; } // If it's a keyword name... if( token.match(/[a-z_]+/i) ) { flushKey(); curKey = token; curValues = []; } // Otherwise it's a value (and hence a number) else { curValues.push( parseFloat(token) ); } return null; } protected function onDone( obj :Object ) :Array { return null; } // abstract protected function flushKey() :void { if ( curKey ) { // If there is only one value for the key, we don't need to wrap // it in an array. if ( curValues.length == 1 ) { obj[ curKey ] = curValues[0]; } else { obj[ curKey ] = curValues; } } } } class Background extends AbstractArgument { public function Background() { } override protected function onDone( obj :Object ) :Array { var adj :Adjustment = new Adjustment; adj.fill( obj ); compiler.compiled[ "background" ] = adj; compiler = null; return [ "general" ]; } } class ShapeAdjustment extends AbstractArgument { private var name :String; private var ruleName :String; public function ShapeAdjustment( args :Array ) { name = args[0]; ruleName = args[1]; } override protected function onDone( obj :Object ) :Array { trace(this + ".onDone(obj :Object ) : " + obj ); var shape :Adjustment = new Adjustment(); shape.name = name; shape.fill( obj ); // We are always adding to the lastest rule we've created. var last :int = compiler.compiled[ ruleName ].length - 1; compiler.compiled[ ruleName ][ last ].draw.push( shape ) compiler = null; return [ "ruleDraw", ruleName ]; } } interface IState { function eat( token :String, compiler :Compiler ) :Array; } class RuleWeight implements IState { private var weight :Number = 1; private var ruleName :String; public function RuleWeight( args :Array ) { ruleName = args[0]; } public function eat( token :String, compiler :Compiler ) :Array { if ( token != "{" ) { weight = parseFloat( token ); return null; } else { // "{" compiler.compiled[ ruleName ].push({ weight: weight, draw: [] }); return [ "ruleDraw", ruleName ]; } } } class General implements IState { public function General(){} public function eat( token :String, compiler :Compiler ) :Array { return [ token ]; } }