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

Tone Designer

配色をする際、トーンを計画したあとに色相を調整すると楽に配色が決まります. 
トーンベースで配色を考えることができるツールが欲しくて作ってみました.
(トーンとは明度と彩度のペアに名前を付けたものです.
”カラートーン” で検索すると参考になるページが色々ヒットすると思います.)

素敵な配色ができたらぜひforkしてね!
TonePresetを編集するとプリセットのコンボボックスの内容が変わります.

2010.5.22 
save時の出力とファイル名を修正. 
プリセット LOLLIPOP、HYSTEIC、VIDEO_GAME80Sを追加

@author imajuk

import com.bit101.components.RangeSlider;
Get Adobe Flash player
by imajuk 22 May 2010
/**
 * Copyright imajuk ( http://wonderfl.net/user/imajuk )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/hamj
 */

package  
{
    /**
     * 配色をする際、トーンを計画したあとに色相を調整すると楽に配色が決まります. 
     * トーンベースで配色を考えることができるツールが欲しくて作ってみました.
     * (トーンとは明度と彩度のペアに名前を付けたものです.
     *  ”カラートーン” で検索すると参考になるページが色々ヒットすると思います.)
     * 
     * 素敵な配色ができたらぜひforkしてね!
     * TonePresetを編集するとプリセットのコンボボックスの内容が変わります.
     * 
     * 2010.5.22 
     *      save時の出力とファイル名を修正. 
     *      プリセット LOLLIPOP、HYSTEIC、VIDEO_GAME80Sを追加
     * 
     * @author imajuk
     */
     
    import com.bit101.components.Label;
    import com.bit101.components.Window;
//    import com.bit101.components.RangeSlider;
    import com.bit101.components.Knob;
    import com.bit101.components.PushButton;
    import com.bit101.components.ComboBox;
    import com.bit101.components.HUISlider;
    import com.bit101.components.Panel;
    import flash.net.FileReference;
    import flash.utils.ByteArray;
    import flash.display.BitmapData;
    import flash.utils.Dictionary;
    import flash.events.Event;
    import flash.display.DisplayObject;
    import flash.display.StageScaleMode;
    import flash.display.StageAlign;
    import flash.display.Sprite;
    import mx.graphics.codec.PNGEncoder;

    public class ToneDesigner extends Sprite 
    {
        //=================================
        // model
        //=================================
        private var tones : ToneCollection;
        //=================================
        // view
        //=================================
        private var sample1 : Sample;
        private var sample2 : Sample;
        private var sContainer : Sprite;
        private var output : Output;
        //=================================
        // UI
        //=================================
        private var panel : Panel;
        private var comps : Array = [];
        private var sliders : Array = [];
        private var toneItems : Array;
        private var suspendSliderUpdating : Boolean;
        private var dic : Dictionary = new Dictionary(true);

        public function ToneDesigner()
        {
            //=================================
            // stage setting
            //=================================
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            
            //=================================
            // prepare view
            //=================================
            sContainer = addChild(new Sprite()) as Sprite;
            sample1 = sContainer.addChild(new Sample(100, 400)) as Sample;
            sample2 = sContainer.addChild(new Sample(100, 400)) as Sample;
            
            panel = addChild(new Panel(this, 329, 5)) as Panel;
            panel.setSize(120, 440);
            panel.alpha = .8;
        
            output = stage.addChild(new Output()) as Output;
            output.x = 300;
            output.y = 25;
            output.visible = false;
            
            //=================================
            // prepare model
            //================================= 
            tones = TonePreset.getPreset(TonePreset.COOL);
            //for Tone selector
            toneItems = 
               ColorTone.getAll().map(
                   function(toneKind : String, ...param):ColorTone
                    {
                        return new ColorTone(toneKind, 10);
                    }
                );
            
            update();
        }
        
        private function update() : void 
        {
            buildUI();
            draw();
            layout();
        }

        private function buildUI() : void 
        {
            //=================================
            // reset components
            //=================================
            comps.forEach(
                function(d : DisplayObject, ...param):void
                {
                    DisplayObjectUtil.removeChild(d);
                });
            comps = [];
            sliders = [];
            
            var cx : int = 10;
            var cy : int = 10;
            
            //=================================
            // preset selector
            //=================================
            var ps:ComboBox = new ComboBox(panel, cx, cy, "presets");
            TonePreset.getAll().forEach(
                function(tone:ToneCollection, ...param):void
                {
                    ps.addItem(tone);
                }
            );
            ps.listItemHeight = 15;
            ps.setSize(100, 15);
            ps.addEventListener(
                    Event.SELECT, 
                    function():void
                    {
                        tones = ToneCollection(ps.selectedItem);
                        update();
                    }
                );
            
            cy += ps.height + 10;
            comps.push(ps);
            
            //=================================
            // tone controlers
            //=================================
            tones.forEach(
                function(tone : ColorTone, idx : int, ...param):void
                {
                    //=================================
                    // remove tone button
                    //=================================
                    var minus : PushButton = 
                        new PushButton(panel, cx, cy + 1, "-", 
                        function():void
                        {
                            if (tones.length <= 1)
                               return;
                            
                            //remove a tone
                            tones.remove(idx);
                            
                            //re-calc data
                            tones.increase(getToneFromSliderIndex(idx).value / tones.length);
                            
                            update();
                        });
                    minus.setSize(13, 13);
                    comps.push(minus);
                        
                    //=================================
                    // add tone button
                    //=================================
                    var plus : PushButton = 
                        new PushButton(panel, cx, cy + 15, "+", 
                        function():void
                        {
                            //create new tone
                            var newTone : ColorTone = toneItems[0].clone();
                            
                            //re-calc data
                            tones.decrease(newTone.value / sliders.length);
                            
                            //insert created one
                            tones.add(idx + 1, newTone);
                            
                            //suspend slider's event handlers
                            suspendSliderUpdating = true;
                            
                            update();
                            
                            //recover slider's event handlers
                            suspendSliderUpdating = false;
                                
                        });
                    plus.setSize(13, 13);
                    comps.push(plus);
                    
                    //=================================
                    // tone selector
                    //=================================
                    var combo : ComboBox = new ComboBox(panel, cx + 20, cy, tone.label);
                    
                    //set combobox items
                    toneItems.forEach(
                        function(item:ColorTone, ...param):void
                        {
                            combo.addItem(item);
                        }
                    );
                    
                    //set up selector
                    combo.addEventListener(
                        Event.SELECT, 
                        function():void
                        {
                            var t : ColorTone = ColorTone(combo.selectedItem).clone();
                            t.value = tones.getToneAmount(idx);
                            dic[slider] = tones.replace(idx, t); 
                            draw();
                        }
                    );
                    combo.setSize(80, 13);
                    comps.push(combo);
                    
                    //=================================
                    // slider for tone amount
                    //=================================
                    var slider : HUISlider = 
                        new HUISlider(
                            panel, cx + 11, cy + 12, "", 
                            function(e : Event):void                
                            {       
                                if (sliders.length <= 1)
                                    return;
                                           
                                if (suspendSliderUpdating)
                                    return;
                                                    
                                //re-calc data
                                calc(idx, HUISlider(e.target));
                                        
                                draw();
                    
                            });
                    slider.setSliderParams(0, 100, tones.getToneAmount(idx));
                    slider.setSize(115, 15);
                    comps.push(slider);
                    sliders.push(slider);
                    
                    //register th tone to dictionary in order to find out it from reference of slider
                    dic[slider] = tones.getTone(idx);
                    
                    cy += combo.height + slider.height + 5;
                });
                
            
            //=================================
            // hue adjustment controlers
            //=================================
            var me:Sprite = this;
            var hue : PushButton = 
                new PushButton(
                    panel, cx, stage.stageHeight - 75, "hue adjustment", 
                    function():void
                    {
                        var window:Window = new Window(me, 275, 290, "hue adjustment");
                        window.setSize(120, 155);
//                        window.hasCloseButton = true;
                        window.alpha = .8;
                        window.addEventListener(
                            Event.CLOSE, 
                            function():void
                            {
                                window.removeEventListener(Event.CLOSE, arguments.callee);
                                me.removeChild(window);
                            }
                        );
        
                        var wx:int = 10;
                        var wy:int = 25;
                        
                        //=================================
                        // label
                        //=================================
                        var label:Label = new Label(window, wx+25, wy, "hue range");
                        wy += 20;
                        
                        //=================================
                        // hue range
                        //=================================
                        var range : RangeSlider = new RangeSlider(RangeSlider.HORIZONTAL, window, wx, wy);
                        range.labelMode = "always";
                        range.labelPosition = "bottom";
                        range.setSize(95, 10);
                        range.minimum = 0;
                        range.maximum = 360;
                        range.highValue = tones.hueMax;
                        range.lowValue = tones.hueMin;
                        range.addEventListener(
                            Event.CHANGE, 
                            function():void
                            {
                                tones.hueMin = range.lowValue;
                                tones.hueMax = range.highValue;
                                draw();
                            }
                        );
                        wy += range.height + 15;
                        
                        //=================================
                        // hue wheel
                        //=================================
                        var knob:Knob = 
                            new Knob(
                                window, label.x, wy, "hue wheel", 
                                function():void
                                {
                                    tones.hueRotation = knob.value; 
                                    draw();
                                });
                        knob.maximum = 360;
                        knob.value = tones.hueRotation;
                        wy += knob.height;
                        
                        //=================================
                        // close button
                        //=================================
                        var close : PushButton = new PushButton(window, 105, 5, "", function():void
                        {
                            window.dispatchEvent(new Event(Event.CLOSE));
                        });
                        close.setSize(10, 10);
                    });
            comps.push(hue);
            
            //=================================
            // save button
            //=================================
            var save : PushButton = 
                new PushButton(
                    panel, cx, stage.stageHeight - 50, "save", 
                    function():void
                    {
                        var b : BitmapData = 
                            new BitmapData(stage.stageWidth, stage.stageHeight, false, 0xFFFFFF);
                        panel.visible = false;
                        output.visible = true;
                        b.draw(stage);
                        panel.visible = true;
                        output.visible = false;
                        var encorder : PNGEncoder = new PNGEncoder();
                        var bytes : ByteArray = encorder.encode(b);
                        var fr : FileReference = new FileReference();
                        fr.save(bytes, "tones" + int(Math.random() * 0xFFFFFF) + ".png");
                    });
            save.setSize(45, 20);
            comps.push(save);
            
            //=================================
            // redraw button
            //=================================
            var redraw : PushButton = 
                new PushButton(
                    panel, cx + save.width, stage.stageHeight - 50, "redraw", 
                    function():void
                    {
                        draw();
                    }
                );
            redraw.setSize(45, 20);
            comps.push(redraw);
        }

        private function draw() : void 
        {
            sample1.draw(tones, 100);
            sample2.draw(tones);
            
            output.update(tones);
        }
        
        private function layout() : void 
        {
            var margin:int = 4;
            sample2.x = sample1.canvasWidth + margin;
            sContainer.x = int((stage.stageWidth - panel.width - (sample1.canvasWidth * 2 + margin)) * .5); 
            sContainer.y = int((stage.stageHeight - sample1.canvasHeight) * .5); 
        }

        private function getToneFromSliderIndex(idx : int) : ColorTone 
        {
            return getToneFromSlider(sliders[idx]);
        }
        
        private function getToneFromSlider(slider : HUISlider) : ColorTone 
        {
            return ColorTone(dic[slider]);
        }

        private function calc(idx : Number, changed : HUISlider) : void 
        {
            //suspend slider's event handlers
            suspendSliderUpdating = true;
            
            var diff : Number = (tones.getToneAmount(idx) - changed.value);
            tones.setToneAmount(idx, changed.value);
            
            var a : Number = diff / (sliders.length - 1);
            
            var others : Array = 
                sliders.filter(
                    function(s : HUISlider, ...param):Boolean
                    {
                        var v : Number = getToneFromSlider(s).value + a;
                        return (s != changed) && (v >= 0 && v <= 100);
                    }
                );
            
            others.forEach(
                function(s : HUISlider, ...param):void
                {
                    var tone:ColorTone = getToneFromSlider(s);
                    tone.value += diff / others.length;
                    s.value = tone.value; 
                }
            );
            
            //recover slider's event handlers
            suspendSliderUpdating = false;
        }
    }
}


import com.bit101.components.Style;
import com.bit101.components.Label;
import com.bit101.components.Component;
import com.bit101.components.Text;
import flash.display.DisplayObjectContainer;
import flash.display.DisplayObject;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.geom.Rectangle;
import flash.display.Shape;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.Bitmap;

import fl.motion.easing.Exponential;
import frocessing.color.ColorHSV;


/**
 * preset of ToneCollection
 */
class TonePreset 
{
    /**
     * プリセットの名前です.
     * 整数はidなので重複しないように注意!
     */
    public static const MODERN : int = 0;
    public static const COOL : int = 1;
    public static const CLASSIC : int = 2;
    public static const SUMMER : int = 3;
    public static const JAPANESE : int = 4;
    public static const LOLLIPOP : int = 5;
    public static const HYSTEIC : int = 6;
    public static const VIDEO_GAME80S : int = 7;
    
    /**
     * すべてのプリセットの配列を返します.
     * プリセットを追加した場合はここも忘れずに編集してね!.
     */
    public static function getAll() : Array 
    {
        return [
            getPreset(COOL), 
            getPreset(MODERN), 
            getPreset(LOLLIPOP),
            getPreset(CLASSIC), 
            getPreset(SUMMER), 
            getPreset(HYSTEIC),
            getPreset(JAPANESE),
            getPreset(VIDEO_GAME80S)
        ];
    }
    
    /**
     * プリセットのトーンを変えたい時はcase文を編集
     */
    public static function getPreset(id : int) : ToneCollection 
    {
        var preset:ToneCollection;
        
        switch(id)
        {
            case MODERN:
                preset = 
                    new ToneCollection(
                        "MODERN",
                        [
                            new ColorTone(ColorTone.LIGHT_GRAYISH, 3),
                            new ColorTone(ColorTone.PALE, 12), 
                            new ColorTone(ColorTone.LIGHT, 7),
                            new ColorTone(ColorTone.DARK, 21),
                            new ColorTone(ColorTone.VIVID, 1),
                            new ColorTone(ColorTone.DARK_GRAYISH, 9),
                            new ColorTone(ColorTone.PALE, 34),
                            new ColorTone(ColorTone.BRIGHT, 4),
                            new ColorTone(ColorTone.LIGHT_GRAYISH, 9)
                        ],
                        0,360
                    );
                break;
            
            case COOL:
                preset =
                    new ToneCollection(
                        "COOL",
                        [
                            new ColorTone(ColorTone.LIGHT_GRAYISH, 3),
                            new ColorTone(ColorTone.PALE, 12), 
                            new ColorTone(ColorTone.LIGHT, 7),
                            new ColorTone(ColorTone.DARK, 21),
                            new ColorTone(ColorTone.VIVID, 1),
                            new ColorTone(ColorTone.DARK_GRAYISH, 9),
                            new ColorTone(ColorTone.DEEP, 34),
                            new ColorTone(ColorTone.DULL, 4),
                            new ColorTone(ColorTone.LIGHT_GRAYISH, 9)
                        ],
                        240, 360
                    );
                break; 
                
            case CLASSIC:
                preset =
                    new ToneCollection(
                        "CLASSIC",
                        [
                            new ColorTone(ColorTone.LIGHT_GRAYISH, 2),
                            new ColorTone(ColorTone.DARK, 14), 
                            new ColorTone(ColorTone.BRIGHT, 2),
                            new ColorTone(ColorTone.PALE, 19),
                            new ColorTone(ColorTone.DULL, 15),
                            new ColorTone(ColorTone.DARK_GRAYISH, 46),
                            new ColorTone(ColorTone.LIGHT_GRAYISH, 2)
                        ],
                        36, 71
                    );
                break; 
                
            case SUMMER:
                preset =
                    new ToneCollection(
                        "SUMMER",
                        [
                            new ColorTone(ColorTone.PALE, 11), 
                            new ColorTone(ColorTone.LIGHT, 8),
                            new ColorTone(ColorTone.VIVID, 3),
                            new ColorTone(ColorTone.STRONG, 12),
                            new ColorTone(ColorTone.PALE, 61),
                            new ColorTone(ColorTone.SOFT, 5)
                        ],
                        125, 217
                    );
                break;
                
            case JAPANESE:
                preset =
                    new ToneCollection(
                        "JAPANESE TRAD",
                        [
                            new ColorTone(ColorTone.LIGHT_GRAYISH, 8), 
                            new ColorTone(ColorTone.GRAYISH, 2), 
                            new ColorTone(ColorTone.PALE, 5),
                            new ColorTone(ColorTone.DARK, 33),
                            new ColorTone(ColorTone.DULL, 4),
                            new ColorTone(ColorTone.LIGHT_GRAYISH, 38),
                            new ColorTone(ColorTone.PALE, 10)
                        ],
                        260, 360, 65
                    );
                break; 
                
            case LOLLIPOP:
                preset =
                    new ToneCollection(
                        "LOLLIPOP",
                        [
                            new ColorTone(ColorTone.VIVID, 2), 
                            new ColorTone(ColorTone.PALE, 11),
                            new ColorTone(ColorTone.LIGHT, 22),
                            new ColorTone(ColorTone.PALE, 4),
                            new ColorTone(ColorTone.VIVID, 61)
                        ],
                        297, 360, 11
                    );
                break; 
                
            case HYSTEIC:
                preset =
                    new ToneCollection(
                        "HYSTEIC",
                        [
                            new ColorTone(ColorTone.PALE, 7), 
                            new ColorTone(ColorTone.STRONG, 3),
                            new ColorTone(ColorTone.VIVID, 20),
                            new ColorTone(ColorTone.STRONG, 3),
                            new ColorTone(ColorTone.BRIGHT, 51),
                            new ColorTone(ColorTone.PALE, 16)
                        ],
                        178, 360, 72
                    );
                break;
                
            case VIDEO_GAME80S:
                preset =
                    new ToneCollection(
                        "VIDEO_GAME80S",
                        [
                            new ColorTone(ColorTone.DARK_GRAYISH, 8), 
                            new ColorTone(ColorTone.STRONG, 6),
                            new ColorTone(ColorTone.VIVID, 34),
                            new ColorTone(ColorTone.STRONG, 4),
                            new ColorTone(ColorTone.BRIGHT, 18),
                            new ColorTone(ColorTone.DEEP, 28)
                        ],
                        231, 360, 25
                    );
                break; 
        }
        
        return preset;
    }
}

/**
 * collection of Tone
 */
class ToneCollection 
{
    public var label : String;
    public var hueResolver : HueResolver;
    private var data : Array;

    public function ToneCollection(
                        label:String, 
                        data : Array,
                        hueMin : Number, 
                        hueMax : Number,
                        hueRotation : Number = 0
                    ) 
    {
        this.label = label;
        this.data = data;
        this.hueResolver = new HueResolver(this, hueMin, hueMax, hueRotation);
    }

    public function getToneAmount(idx : Number) : Number 
    {
        return data[idx].value;
    }

    public function setToneAmount(idx : Number, value : Number) : void 
    {
        data[idx].value = value;
    }

    public function forEach(closure : Function) : void 
    {
        data.forEach(closure);
    }

    public function remove(idx : int) : void 
    {
        data.splice(idx, 1);
    }

    public function add(idx : int, newTone : ColorTone) : void 
    {
        data.splice(idx, 0, newTone);
    }

    public function replace(idx : int, newTone : ColorTone) : ColorTone 
    {
        data[idx] = newTone;
        return newTone;
    }

    public function get length() : int 
    {
        return data.length;
        return 0;
    }

    public function increase(value : Number) : void 
    {
        forEach(function(tone : ColorTone, ...param):void
        {
            tone.value += value;
        });
    }

    public function decrease(value : Number) : void 
    {
        increase(-value);
    }

    public function getTone(idx : int) : ColorTone 
    {
        return data[idx];
    }

    public function get hueMin() : Number
    {
        return hueResolver.hueMin;
    }

    public function set hueMin(value:Number) : void 
    {
        hueResolver.hueMin = value;
        hueResolver.reset();
    }

    public function get hueMax() : Number
    {
        return hueResolver.hueMax;
    }
    
    public function set hueMax(value:Number) : void 
    {
        hueResolver.hueMax = value;
        hueResolver.reset();
    }

    public function get hueRotation() : Number 
    {
        return hueResolver.hueRotation;
    }

    public function set hueRotation(value:Number) : void 
    {
        hueResolver.hueRotation = value;
    }
}

class ColorTone 
{
    //=================================
    // all kind of Color Tone
    //=================================
    public static const PALE : String           = "PALE";
    public static const LIGHT_GRAYISH : String  = "LIGHT_GRAYISH";
    public static const GRAYISH : String        = "GRAYISH";
    public static const DARK_GRAYISH : String   = "DARK_GRAYISH";
    
    public static const LIGHT : String          = "LIGHT";
    public static const SOFT : String           = "SOFT";
    public static const DULL : String           = "DULL";
    public static const DARK : String           = "DARK";
    
    public static const BRIGHT : String         = "BRIGHT";
    public static const STRONG : String         = "STRONG";
    public static const DEEP : String           = "DEEP";
    public static const VIVID : String          = "VIVID";
    
    //=================================
    // defination of Color Tone
    // [彩度min, 彩度max, 明度min, 明度max]
    //=================================
    internal static const DEF_PALE : Array            = [7, 20, 100, 100];
    internal static const DEF_LIGHT_GRAYISH : Array   = [5, 20, 70, 90];
    internal static const DEF_GRAYISH : Array         = [5, 20, 40, 50];
    internal static const DEF_DARK_GRAYISH : Array    = [5, 10, 7, 32];
    
    internal static const DEF_LIGHT : Array           = [30, 68, 100, 100];
    internal static const DEF_SOFT : Array            = [30, 68, 80, 90];
    internal static const DEF_DULL : Array            = [30, 50, 40, 70];
    internal static const DEF_DARK : Array            = [50, 65, 10, 30];
    
    internal static const DEF_BRIGHT : Array          = [90, 100, 77, 90];
    internal static const DEF_STRONG : Array          = [100, 100, 47, 67];
    internal static const DEF_DEEP : Array            = [100, 100, 40, 45];
    internal static const DEF_VIVID : Array           = [100, 100, 100, 100];

    public static function getToneAs(toneKind : String, hue : Number) : uint 
    {
        var tone : Array = ColorTone["DEF_" + toneKind];
        var satiation : Number = MathUtil.random(tone[0], tone[1]);
        var value : Number = MathUtil.random(tone[2], tone[3]);
        return new ColorHSV(hue, satiation * .01, value * .01).value;
    }

    public static function getAll() : Array 
    {
        return [PALE, LIGHT_GRAYISH, GRAYISH, DARK_GRAYISH, LIGHT, SOFT, DULL, DARK, BRIGHT, STRONG, DEEP, VIVID];
    }

    public var label : String;
    public var value : Number;
    
    /**
     * コンストラクタ
     * @param kind  トーンの種類. すべてのトーンの種類はColorToneに定義されています.
     * @param value 全体に占めるこのトーンの割合
     */
    public function ColorTone(kind : String, value : Number) 
    {
        this.label = kind;
        this.value = value;
    }
    
    public function toString() : String 
    {
        return label + " : " + int(value) + "%";
    }

    public function clone() : ColorTone 
    {
        return new ColorTone(label, value);
    }
}

class HueResolver 
{
    public var hueMin : Number;
    public var hueMax : Number;
    private var tones : ToneCollection;
    private var internalHue : Array;
    private var hue : Array;
    private var cursor : int = 0;
    private var _hueRotation : Number;
    
    public function set hueRotation(hueRotation : Number) : void
    {
        _hueRotation = hueRotation;
        rotate();
    }
    
    public function get hueRotation() : Number
    {
        return _hueRotation;
    }

    public function HueResolver(
                        tones : ToneCollection, 
                        hueMin : Number, 
                        hueMax : Number,
                        hueRotation : Number = 0
                    )         
    {
        this.tones = tones;
        this.hueMin = hueMin;
        this.hueMax = hueMax;
        this._hueRotation = hueRotation;
        
        reset();
    }

    public function next() : Number 
    {
        cursor ++;
        if (cursor >= 1000)
            cursor = 0;
            
        return hue[cursor];
    }

    public function reset() : void 
    {
        internalHue = [];
        for (var i : int = 0;i < 1000;i++) 
        {
            internalHue.push(MathUtil.random(hueMin, hueMax));
        }
        rotate();
    }

    private function rotate() : void 
    {
        hue = 
            internalHue.map(
                function(h : int, ...param):Number
                {
                    return h + _hueRotation;
                }
            );
    }
}

class Sample extends Sprite 
{
    public var canvasWidth : int;
    public var canvasHeight : int;
    private var canvas : BitmapData;
    private var box : Sprite;
    private var bMask : Shape;
    private var rect : Rectangle = new Rectangle();

    public function Sample(canvasWidth:int, canvasHeight : int)
    {
        this.canvasWidth = canvasWidth;
        this.canvasHeight = canvasHeight;
        
        canvas = new BitmapData(450, 450, false, 0xFFFFFF);
        box = addChild(new Sprite()) as Sprite;
        box.addChild(new Bitmap(canvas));
        bMask = box.addChild(new Shape()) as Shape;
        box.mask = bMask;
    }

    public function draw(
                        tones:ToneCollection, 
                        max:int = 1
                    ) : void 
    {
        var px : int = 0;
        var py : int = 0;
        
        //clear canvas
        canvas.fillRect(canvas.rect, 0xFFFFFF);
        
        //draw
        tones.forEach(
            function(tone : ColorTone, ...param):void
            {
                var internalY : int = 0;
                var toneHeight : int = canvasHeight * tone.value * .01;
                
                while(internalY < toneHeight)
                {
                    var rHeight : int = Exponential.easeIn(Math.random(), 1, max - 1, 1);
                    if (rHeight == 0)
                        rHeight = 1;
                        
                    rect.x = px;
                    rect.y = py + internalY;
                    rect.width = canvasWidth;
                    rect.height = rHeight;
                    
                    internalY += rHeight;
                    if (internalY > toneHeight)
                        rect.height = rHeight - (internalY - toneHeight);
                    
                    canvas.fillRect(
                        rect, 
                        ColorTone.getToneAs(tone.label, tones.hueResolver.next())
                    );
                }
                py += toneHeight;
            }
        );
        
        bMask.graphics.clear();
        bMask.graphics.beginFill(0);
        bMask.graphics.drawRoundRect(0, 0, rect.width, rect.y + rect.height, canvasHeight * .025);
    }
}

class Output extends Sprite 
{
    private var ta : Text;

    public function Output()
    {
        ta = addChild(new Text()) as Text;
        ta.setSize(130, 250);
    }

    public function update(tones : ToneCollection) : void 
    {
        var r:HueResolver = tones.hueResolver;
        var h1:Number = int((r.hueMin + r.hueRotation) % 360); 
        var h2:Number = int((r.hueMax + r.hueRotation) % 360); 
        ta.text = 
           tones.label + " : \n\n" + 
           "  hue(" + h1 + " ... " + h2 +")\n" +
           "  ---------------------------\n";
           
        tones.forEach(
            function(tone:ColorTone, ...param):void
            {
                ta.text += "    " + tone +"\n";
            }
        );
    }
}

class MathUtil
{
    /**
     * 任意の範囲からランダムな数値を返す
     */
    public static function random(min:Number, max:Number):Number
    {
        return Math.random() * (max - min) + min;
    }
}

class DisplayObjectUtil 
{
    /**
     * DisplayObjectContainer.removeChild()は、
     * 削除したいchildがコンテナに含まれているかどうか確認しません.
     * もし、childがコンテナに含まれていない場合はランタイムエラーになります.
     * このメソッドはchildを削除する前にそのchildがchildがコンテナに
     * 含まれているかどうかをチェックします.
     * 
     * 明示的にランタイムエラーをキャッチしたい場合は使用しないで下さい.
     */
    public static function removeChild(child:DisplayObject):DisplayObject
    {
        if (!child)
            return child;
            
        var container:DisplayObjectContainer = child.parent;
        if (!container)
            return child;
            
        if (container.contains(child))
            container.removeChild(child);
            
        return child;
    }
}

class RangeSlider extends Component
{
    protected var _back:Sprite;
    protected var _highLabel:Label;
    protected var _highValue:Number = 100;
    protected var _labelMode:String = ALWAYS;
    protected var _labelPosition:String;
    protected var _labelPrecision:int = 0;
    protected var _lowLabel:Label;
    protected var _lowValue:Number = 0;
    protected var _maximum:Number = 100;
    protected var _maxHandle:Sprite;
    protected var _minimum:Number = 0;
    protected var _minHandle:Sprite;
    protected var _orientation:String = VERTICAL;
    protected var _tick:Number = 1;
    
    public static const ALWAYS:String = "always";
    public static const BOTTOM:String = "bottom";
    public static const HORIZONTAL:String = "horizontal";
    public static const LEFT:String = "left";
    public static const MOVE:String = "move";
    public static const NEVER:String = "never";
    public static const RIGHT:String = "right";
    public static const TOP:String = "top";
    public static const VERTICAL:String = "vertical";
    
    
    
    /**
     * Constructor
     * @param orientation Whether the slider will be horizontal or vertical.
     * @param parent The parent DisplayObjectContainer on which to add this Slider.
     * @param xpos The x position to place this component.
     * @param ypos The y position to place this component.
     * @param defaultHandler The event handling function to handle the default event for this component (change in this case).
     */
    public function RangeSlider(orientation:String, parent:DisplayObjectContainer=null, xpos:Number=0, ypos:Number=0, defaultHandler:Function = null)
    {
        _orientation = orientation;
        super(parent, xpos, ypos);
        if(defaultHandler != null)
        {
            addEventListener(Event.CHANGE, defaultHandler);
        }
    }
    
    /**
     * Initializes the component.
     */
    protected override function init():void
    {
        super.init();
        if(_orientation == HORIZONTAL)
        {
            setSize(110, 10);
            _labelPosition = TOP;
        }
        else
        {
            setSize(10, 110);
            _labelPosition = RIGHT;
        }
    }
    
    /**
     * Creates and adds the child display objects of this component.
     */
    protected override function addChildren():void
    {
        super.addChildren();
        _back = new Sprite();
        _back.filters = [getShadow(2, true)];
        addChild(_back);
        
        _minHandle = new Sprite();
        _minHandle.filters = [getShadow(1)];
        _minHandle.addEventListener(MouseEvent.MOUSE_DOWN, onDragMin);
        _minHandle.buttonMode = true;
        _minHandle.useHandCursor = true;
        addChild(_minHandle);
        
        _maxHandle = new Sprite();
        _maxHandle.filters = [getShadow(1)];
        _maxHandle.addEventListener(MouseEvent.MOUSE_DOWN, onDragMax);
        _maxHandle.buttonMode = true;
        _maxHandle.useHandCursor = true;
        addChild(_maxHandle);           
        
        _lowLabel = new Label(this);
        _highLabel = new Label(this);
        _lowLabel.visible = (_labelMode == ALWAYS);
    }
    
    /**
     * Draws the back of the slider.
     */
    protected function drawBack():void
    {
        _back.graphics.clear();
        _back.graphics.beginFill(Style.BACKGROUND);
        _back.graphics.drawRect(0, 0, _width, _height);
        _back.graphics.endFill();
    }
    
    /**
     * Draws the handles of the slider.
     */
    protected function drawHandles():void
    {   
        _minHandle.graphics.clear();
        _minHandle.graphics.beginFill(Style.BUTTON_FACE);
        _maxHandle.graphics.clear();
        _maxHandle.graphics.beginFill(Style.BUTTON_FACE);
        if(_orientation == HORIZONTAL)
        {
            _minHandle.graphics.drawRect(1, 1, _height - 2, _height - 2);
            _maxHandle.graphics.drawRect(1, 1, _height - 2, _height - 2);
        }
        else
        {
            _minHandle.graphics.drawRect(1, 1, _width - 2, _width - 2);
            _maxHandle.graphics.drawRect(1, 1, _width - 2, _width - 2);
        }
        _minHandle.graphics.endFill();
        positionHandles();
    }
    
    /**
     * Adjusts positions of handles when value, maximum or minimum have changed.
     * TODO: Should also be called when slider is resized.
     */
    protected function positionHandles():void
    {
        var range:Number;
        if(_orientation == HORIZONTAL)
        {
            range = _width - _height * 2;
            _minHandle.x = (_lowValue - _minimum) / (_maximum - _minimum) * range;
            _maxHandle.x = _height + (_highValue - _minimum) / (_maximum - _minimum) * range;
        }
        else
        {
            range = _height - _width * 2;
            _minHandle.y = _height - _width - (_lowValue - _minimum) / (_maximum - _minimum) * range;
            _maxHandle.y = _height - _width * 2 - (_highValue - _minimum) / (_maximum - _minimum) * range;
        }
        updateLabels();
    }
    
    /**
     * Sets the text and positions the labels.
     */
    protected function updateLabels():void
    {
        _lowLabel.text = getLabelForValue(lowValue);
        _highLabel.text = getLabelForValue(highValue);
        _lowLabel.draw();
        _highLabel.draw();

        if(_orientation == VERTICAL)
        {
            _lowLabel.y = _minHandle.y + (_width - _lowLabel.height) * 0.5;
            _highLabel.y = _maxHandle.y + (_width - _highLabel.height) * 0.5;
            if(_labelPosition == LEFT)
            {
                _lowLabel.x = -_lowLabel.width - 5;
                _highLabel.x = -_highLabel.width - 5;
            }
            else
            {
                _lowLabel.x = _width + 5;
                _highLabel.x = _width + 5;
            }
        }
        else
        {
            _lowLabel.x = _minHandle.x - _lowLabel.width + _height;
            _highLabel.x = _maxHandle.x;
            if(_labelPosition == BOTTOM)
            {
                _lowLabel.y = _height + 2;
                _highLabel.y = _height + 2;
            }
            else
            {
                _lowLabel.y = -_lowLabel.height;
                _highLabel.y = -_highLabel.height;
            }
            
        }
    }

    /**
     * Generates a label string for the given value.
     * @param value The number to create a label for.
     */
    protected function getLabelForValue(value:Number):String
    {
        var str:String = (Math.round(value * Math.pow(10, _labelPrecision)) / Math.pow(10, _labelPrecision)).toString();
        if(_labelPrecision > 0)
        {
            var decimal:String = str.split(".")[1] || "";
            if(decimal.length == 0) str += ".";
            for(var i:int = decimal.length; i < _labelPrecision; i++)
            {
                str += "0";
            }
        }
        return str;
    }
    
    ///////////////////////////////////
    // public methods
    ///////////////////////////////////
    
    /**
     * Draws the visual ui of the component.
     */
    override public function draw():void
    {
        super.draw();
        drawBack();
        drawHandles();
    }
    

    
    
    
    ///////////////////////////////////
    // event handlers
    ///////////////////////////////////
    
    /**
     * Internal mouseDown handler for the low value handle. Starts dragging the handle.
     * @param event The MouseEvent passed by the system.
     */
    protected function onDragMin(event:MouseEvent):void
    {
        stage.addEventListener(MouseEvent.MOUSE_UP, onDrop);
        stage.addEventListener(MouseEvent.MOUSE_MOVE, onMinSlide);
        if(_orientation == HORIZONTAL)
        {
            _minHandle.startDrag(false, new Rectangle(0, 0, _maxHandle.x - _height, 0));
        }
        else
        {
            _minHandle.startDrag(false, new Rectangle(0, _maxHandle.y + _width, 0, _height - _maxHandle.y - _width * 2));
        }
        if(_labelMode == MOVE)
        {
            _lowLabel.visible = true;
            _highLabel.visible = true;
        }
    }
    
    /**
     * Internal mouseDown handler for the high value handle. Starts dragging the handle.
     * @param event The MouseEvent passed by the system.
     */
    protected function onDragMax(event:MouseEvent):void
    {
        stage.addEventListener(MouseEvent.MOUSE_UP, onDrop);
        stage.addEventListener(MouseEvent.MOUSE_MOVE, onMaxSlide);
        if(_orientation == HORIZONTAL)
        {
            _maxHandle.startDrag(false, new Rectangle(_minHandle.x + _height, 0, _width - _height - _minHandle.x - _height, 0));
        }
        else
        {
            _maxHandle.startDrag(false, new Rectangle(0, 0, 0, _minHandle.y - _width));
        }
        if(_labelMode == MOVE)
        {
            _lowLabel.visible = true;
            _highLabel.visible = true;
        }
    }
    
    /**
     * Internal mouseUp handler. Stops dragging the handle.
     * @param event The MouseEvent passed by the system.
     */
    protected function onDrop(event:MouseEvent):void
    {
        stage.removeEventListener(MouseEvent.MOUSE_UP, onDrop);
        stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMinSlide);
        stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMaxSlide);
        stopDrag();
        if(_labelMode == MOVE)
        {
            _lowLabel.visible = false;
            _highLabel.visible = false;
        }
    }
    
    /**
     * Internal mouseMove handler for when the low value handle is being moved.
     * @param event The MouseEvent passed by the system.
     */
    protected function onMinSlide(event:MouseEvent):void
    {
        var oldValue:Number = _lowValue;
        if(_orientation == HORIZONTAL)
        {
            _lowValue = _minHandle.x / (_width - _height * 2) * (_maximum - _minimum) + _minimum;
        }
        else
        {
            _lowValue = (_height - _width - _minHandle.y) / (height - _width * 2) * (_maximum - _minimum) + _minimum;
        }
        if(_lowValue != oldValue)
        {
            dispatchEvent(new Event(Event.CHANGE));
        }
        updateLabels();
    }

    /**
     * Internal mouseMove handler for when the high value handle is being moved.
     * @param event The MouseEvent passed by the system.
     */
    protected function onMaxSlide(event:MouseEvent):void
    {
        var oldValue:Number = _highValue;
        if(_orientation == HORIZONTAL)
        {
            _highValue = (_maxHandle.x - _height) / (_width - _height * 2) * (_maximum - _minimum) + _minimum;
        }
        else
        {
            _highValue = (_height - _width * 2 - _maxHandle.y) / (_height - _width * 2) * (_maximum - _minimum) + _minimum;
        }
        if(_highValue != oldValue)
        {
            dispatchEvent(new Event(Event.CHANGE));
        }
        updateLabels();
    }
    
    /**
     * Gets / sets the minimum value of the slider.
     */
    public function set minimum(value:Number):void
    {
        _minimum = value;
        _maximum = Math.max(_maximum, _minimum);
        _lowValue = Math.max(_lowValue, _minimum);
        _highValue = Math.max(_highValue, _minimum);
        positionHandles();
    }
    public function get minimum():Number
    {
        return _minimum;
    }

    /**
     * Gets / sets the maximum value of the slider.
     */
    public function set maximum(value:Number):void
    {
        _maximum = value;
        _minimum = Math.min(_minimum, _maximum);
        _lowValue = Math.min(_lowValue, _maximum);
        _highValue = Math.min(_highValue, _maximum);
        positionHandles();
    }
    public function get maximum():Number
    {
        return _maximum;
    }

    /**
     * Gets / sets the low value of this slider.
     */
    public function set lowValue(value:Number):void
    {
        _lowValue = value;
        _lowValue = Math.min(_lowValue, _highValue);
        _lowValue = Math.max(_lowValue, _minimum);
        positionHandles();
        dispatchEvent(new Event(Event.CHANGE));
    }
    public function get lowValue():Number
    {
        return Math.round(_lowValue / _tick) * _tick;
    }

    /**
     * Gets / sets the high value for this slider.
     */
    public function set highValue(value:Number):void
    {
        _highValue = value;
        _highValue = Math.max(_highValue, _lowValue);
        _highValue = Math.min(_highValue, _maximum);
        positionHandles();
        dispatchEvent(new Event(Event.CHANGE));
    }
    public function get highValue():Number
    {
        return Math.round(_highValue / _tick) * _tick;
    }

    /**
     * Sets / gets when the labels will appear. Can be "never", "move", or "always"
     */
    public function set labelMode(value:String):void
    {
        _labelMode = value;
        _highLabel.visible = (_labelMode == ALWAYS);
        _lowLabel.visible = (_labelMode == ALWAYS);
    }
    public function get labelMode():String
    {
        return _labelMode;
    }

    /**
     * Sets / gets where the labels will appear. "left" or "right" for vertical sliders, "top" or "bottom" for horizontal.
     */
    public function set labelPosition(value:String):void
    {
        _labelPosition = value;
        updateLabels();
    }
    public function get labelPosition():String
    {
        return _labelPosition;
    }

    /**
     * Sets / gets how many decimal points of precisions will be displayed on the labels.
     */
    public function set labelPrecision(value:int):void
    {
        _labelPrecision = value;
        updateLabels();
    }
    public function get labelPrecision():int
    {
        return _labelPrecision;
    }

    /**
     * Gets / sets the tick value of this slider. This round the value to the nearest multiple of this number. 
     */
    public function set tick(value:Number):void
    {
        _tick = value;
        updateLabels();
    }
    public function get tick():Number
    {
        return _tick;
    }


}