ToneCurve
ToneCurveっぽい処理
スプライン補間とBitmapData.paletteMapを使って表現しとります
/**
* Copyright okoi ( http://wonderfl.net/user/okoi )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/7E1Z
*/
//
// ToneCurveっぽい処理
// スプライン補間とBitmapData.paletteMapを使って表現しとります
//
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.display.Bitmap;
import flash.system.Security;
[SWF(width = "465", height = "465")]
/**
*
* @author
*/
public class Main extends Sprite
{
public static const WIDTH:int = 465;
public static const HEIGHT:int = 465;
private var ctrl:ToneCurveController;
private var img:Image;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
Security.allowDomain("assets.wonderfl.net");
Security.loadPolicyFile("http://assets.wonderfl.net/crossdomain.xml");
graphics.beginFill(0x313131);
graphics.drawRect(0, 0, WIDTH, HEIGHT);
graphics.endFill();
ctrl = new ToneCurveController();
ctrl.view.x = WIDTH/2 - ToneCurveController.VIEW_W/2;
ctrl.view.y = HEIGHT - ToneCurveController.VIEW_H - 30;
addChild( ctrl.view );
img = new Image("http://assets.wonderfl.net/images/related_images/6/68/6871/6871afeaf078b092969ac7d2513c209602b0ffea" );
img.view.x = WIDTH / 2;
img.view.y = 287 / 2;
addChild( img.view );
addEventListener( Event.ENTER_FRAME, EnterFrameHandler );
}
private function EnterFrameHandler( e:Event ) : void
{
ctrl.Update();
img.UpdatePaletteMap( ctrl.UpdateColorMap() );
}
}
}
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.net.URLRequest;
import flash.display.Bitmap;
import flash.display.Shape;
import flash.display.Sprite;
import flash.filters.BlurFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.display.Graphics;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.geom.ColorTransform;
/**
* ...
* @author
*/
class Spline
{
private var x:Array;
private var y:Array;
private var z:Array;
/**
* 3次スプライン曲線補間用のテーブルを作成する
* @param p
*/
public function Make( p:/*Point*/Array ) : void
{
var i:int;
var N:int = p.length;
x = new Array(N);
y = new Array(N);
z = new Array(N);
var h:Array = new Array(N);
var d:Array = new Array(N);
for ( i = 0; i < N; i++ )
{
x[i] = p[i].x;
y[i] = p[i].y;
}
z[0] = 0; z[N - 1] = 0;
for ( i = 0; i < N - 1; i++ )
{
h[i] = x[i + 1] - x[i];
d[i + 1] = (y[i + 1] - y[i]) / h[i];
}
z[1] = d[2] - d[1] - h[0] * z[0];
d[1] = 2 * (x[2] - x[0]);
var t:Number;
for ( i = 1; i < N - 2; i++ )
{
t = h[i] / d[i];
z[i + 1] = d[i + 2] - d[i + 1] - z[i] * t;
d[i + 1] = 2 * (x[i + 2] - x[i]) - h[i] * t;
}
z[N - 2] -= h[N - 2] * z[N - 1];
for ( i = N - 2; i > 0; i-- )
{
z[i] = (z[i] - h[i] * z[i + 1]) / d[i];
}
}
/**
* Makeで作成したテーブルから補間値を取り出す
* @param t 取り出す補間値のx座標
* @return
*/
public function GetValue( t:Number ) : Number
{
var i:int, j:int, k:int;
var N:int = z.length;
var h:Number, d:Number;
i = 0; j = N - 1;
while ( i < j )
{
k = (i + j) / 2;
if ( x[k] < t ) i = k + 1;
else j = k;
}
if ( i > 0 ) i--;
h = x[i + 1] - x[i];
d = t - x[i];
return (((z[i + 1] - z[i]) * d / h + z[i] * 3) * d + ((y[i + 1] - y[i]) / h - (z[i] * 2 + z[i + 1]) * h)) * d + y[i];
}
}
/**
* トーンカーブの制御点
* @author
*/
class ToneControlPoint extends Sprite
{
public static const TYPE_LEFT:int = 0;
public static const TYPE_MIDDLE:int = 1;
public static const TYPE_RIGHT:int = 2;
public var type:int;
private var mousedowncallback:Function;
public function ToneControlPoint( callback:Function, t:int )
{
type = t;
graphics.beginFill(0x0);
graphics.lineStyle(1, 0);
if ( type == TYPE_MIDDLE )
{
graphics.drawCircle(0, 0, 3);
}else
{
graphics.drawCircle(0, 0, 6);
}
graphics.endFill();
buttonMode = true;
mousedowncallback = callback;
addEventListener( MouseEvent.MOUSE_DOWN, MouseDownHandler );
addEventListener( Event.REMOVED_FROM_STAGE, RemoveFromStageListener );
}
private function RemoveFromStageListener( e:Event ) : void
{
removeEventListener( Event.REMOVED_FROM_STAGE, RemoveFromStageListener );
removeEventListener( MouseEvent.MOUSE_DOWN, MouseDownHandler );
}
public function set active(flg:Boolean) : void {
var color:ColorTransform;
if ( flg )
{
color = new ColorTransform(1, 1, 1, 1, 255, 0, 0, 0);
}else
{
color = new ColorTransform(1, 1, 1, 1, 0, 0, 0, 0);
}
this.transform.colorTransform = color;
}
private function MouseDownHandler( e:MouseEvent ) : void
{
e.stopPropagation(); // これをやることによって下にイベントがいかなくする
mousedowncallback( this );
}
}
/**
* トーンカーブ部分を管理するクラス
* @author
*/
class ToneCurveController
{
public static const VIEW_W:int = 400;
public static const VIEW_H:int = 150;
public static const CHART_W:int = 390;
public static const CHART_H:int = 140;
public var view:Sprite;
private var _chartview:Sprite; // トーンカーブを描画するVIEW
private var _ctrlPointView:Sprite;
private var _chartLineView:Sprite;
private var _ctrlPointList:/*ToneControlPoint*/Array;
private var _selectPoint:ToneControlPoint = null;
private var _selectPointIndex:int;
private var _drag:Boolean;
private var _spline:Spline;
private var _linecolor:uint;
public function ToneCurveController()
{
view = new Sprite();
// 影
var back:Shape = new Shape();
back.graphics.beginFill(0xc0c0c0);
back.graphics.drawRect(0, 0, VIEW_W, VIEW_H);
back.graphics.endFill();
back.filters = [new BlurFilter()];
view.addChild( back );
_ctrlPointList = new Array();
_spline = new Spline();
CreateChartView();
}
private function CreateChartView() : void
{
var mask:Shape = new Shape();
mask.graphics.beginFill(0xFFFFFF);
mask.graphics.drawRect(0, 0, CHART_W, CHART_H);
mask.graphics.endFill();
_chartview = new Sprite();
_chartview.graphics.beginFill(0xf0f8ff);
_chartview.graphics.beginFill(0xc0c0c0);
_chartview.graphics.drawRect(0, 0, CHART_W, CHART_H);
_chartview.graphics.endFill();
_chartview.mask = mask;
_chartview.addChild( mask );
_linecolor = 0x0;
_chartview.x = 5;
_chartview.y = 5;
_chartview.addEventListener( MouseEvent.MOUSE_DOWN, ChartViewMouseDownHandler );
_chartview.addEventListener( MouseEvent.MOUSE_UP, ChartViewMouseUpHandler );
_chartview.addEventListener( MouseEvent.ROLL_OUT, ChartViewRollOutHandler );
view.addChild( _chartview );
_chartLineView = new Sprite();
_chartview.addChild( _chartLineView );
_ctrlPointView = new Sprite();
_chartview.addChild( _ctrlPointView );
// 両端の点を定義しておく
AddControlPoint( 0, CHART_H, ToneControlPoint.TYPE_LEFT );
AddControlPoint( CHART_W, 0, ToneControlPoint.TYPE_RIGHT );
}
private function OnDrag():void { _drag = true; }
private function OffDrag():void { _drag = false; }
private function ChartViewMouseDownHandler( e:MouseEvent ) : void
{
//trace("ChartViewMouseDownHandler");
var mouseX:Number = _chartview.mouseX;
var mouseY:Number = _chartview.mouseY;
AddControlPoint(mouseX, mouseY, ToneControlPoint.TYPE_MIDDLE);
}
private function ChartViewMouseUpHandler( e:MouseEvent ) : void
{
OffDrag();
}
private function ChartViewRollOutHandler( e:MouseEvent ) : void
{
if ( _selectPoint != null && _drag && _selectPoint.type == ToneControlPoint.TYPE_MIDDLE )
{
DeleteSelectControlPoint();
}
ChartViewMouseUpHandler( null );
}
/**
* 制御点を追加する
* @param x
* @param y
* @param type
*/
private function AddControlPoint(x:Number, y:Number, type:int ) : void
{
// 両端の点より外には出ない
if ( type == ToneControlPoint.TYPE_MIDDLE )
{
if ( x <= _ctrlPointList[0].x ) return;
if ( x >= _ctrlPointList[_ctrlPointList.length - 1].x ) return;
}
var p:ToneControlPoint = new ToneControlPoint(SelectControlPoint, type);
p.x = x;
p.y = y;
var len:int = _ctrlPointList.length;
var i:int;
for ( i = 0; i < len; i++ )
{
if ( x <= _ctrlPointList[i].x )
{
break;
}
}
_ctrlPointList.splice( i, 0, p );
_ctrlPointView.addChild( p );
if ( type == ToneControlPoint.TYPE_MIDDLE )
{
SelectControlPoint( p );
}
}
private function DeleteSelectControlPoint() : void
{
if ( _selectPoint == null ) return;
_ctrlPointList.splice( _selectPointIndex, 1 );
_ctrlPointView.removeChild( _selectPoint );
_selectPoint = null;
ChartViewMouseUpHandler( null );
}
/**
* 制御点を選択
* @param point
*/
private function SelectControlPoint( point:ToneControlPoint ) : void
{
if ( _selectPoint != null )
{
_selectPoint.active = false;
}
_selectPoint = point;
_selectPoint.active = true;
SetSelectPointIndex();
OnDrag();
}
private function SetSelectPointIndex() : void
{
if ( _selectPoint == null )
{
_selectPointIndex = 0;
return;
}
var len:int = _ctrlPointList.length;
for ( var i:int = 0; i < len; i++ )
{
if ( _selectPoint == _ctrlPointList[i] )
{
_selectPointIndex = i;
return;
}
}
}
/**
* 毎フレーム処理
*/
public function Update() : void
{
var i:int;
// 制御点移動
if ( _drag && _selectPoint != null )
{
var mouseX:Number = _chartview.mouseX;
var mouseY:Number = _chartview.mouseY;
var beforeX:Number = _selectPoint.x;
var beforeY:Number = _selectPoint.y;
var nowindex:int = _selectPointIndex;
var nextindex:int = 0;
var len:int = _ctrlPointList.length;
_selectPoint.x = mouseX;
_selectPoint.y = mouseY;
if ( _selectPoint.x < 0 ) _selectPoint.x = 0;
else if ( _selectPoint.x > CHART_W ) _selectPoint.x = CHART_W;
if ( _selectPoint.y < 0 ) _selectPoint.y = 0;
else if ( _selectPoint.y > CHART_H ) _selectPoint.y = CHART_H;
for ( i = 0; i < len; i++ )
{
if ( _selectPoint == _ctrlPointList[i] ) continue;
if ( _selectPoint.x <= _ctrlPointList[i].x )
{
break;
}
nextindex++;
}
if ( nowindex != nextindex )
{
if ( _selectPoint.type == ToneControlPoint.TYPE_MIDDLE )
{
DeleteSelectControlPoint();
}else
{
if ( _selectPoint.type == ToneControlPoint.TYPE_LEFT ) _selectPoint.x = _ctrlPointList[nowindex+1].x - 0.1;
else _selectPoint.x = _ctrlPointList[nowindex-1].x + 0.1;
}
}
}
// スプライン描画処理
var sparray:Array = new Array();
var ctrlpnum:int = _ctrlPointList.length;
for ( i = 0; i < ctrlpnum; i++ )
{
var dx:Number;
var dy:Number;
// ダミー点 (3点以下の時)
if ( i == 0 && ctrlpnum < 3 )
{
dx = _ctrlPointList[i].x + _ctrlPointList[i].x - _ctrlPointList[ctrlpnum - 1].x;
dy = _ctrlPointList[i].y + _ctrlPointList[i].y - _ctrlPointList[ctrlpnum - 1].y;
sparray.push( new Point( dx, dy ) );
}
sparray.push( new Point( _ctrlPointList[i].x, _ctrlPointList[i].y ) );
}
_spline.Make( sparray );
var g:Graphics = _chartLineView.graphics;
g.clear();
g.lineStyle(1, _linecolor);
for ( i = 0; i < CHART_W; i++ )
{
var y:Number = GetSplineValue( i );
if ( y < 0 ) y = 0;
if ( y >= CHART_H ) y = CHART_H - 1;
if ( i == 0 ) g.moveTo( i, y );
else g.lineTo( i, y );
}
}
/**
* スプライン補間で出した値をグラフに合うように整形して取得する
* @param t
* @return
*/
public function GetSplineValue( t:Number ) : Number
{
if ( t < _ctrlPointList[0].x )
{
t = _ctrlPointList[0].x;
}else
if ( t > _ctrlPointList[_ctrlPointList.length - 1].x )
{
t = _ctrlPointList[_ctrlPointList.length - 1].x;
}
var ret:Number = _spline.GetValue(t);
if( ret < 0 ) ret = 0;
else if ( ret >= CHART_H ) ret = CHART_H - 1;
return ret;
}
public function UpdateColorMap() : PaletteMapColorData
{
var colordata:PaletteMapColorData = new PaletteMapColorData();
for ( var i:int = 0; i < 256; i++ )
{
var rate:Number = i / 255;
var x:int = int(rate * (CHART_W - 1));
var y:int = int(GetSplineValue(x));
rate = (1 - y / CHART_H) * 0xFF;
colordata.r.push(toARGB(255, rate, 0, 0));
colordata.g.push(toARGB(255, 0, rate, 0));
colordata.b.push(toARGB(255, 0, 0, rate));
}
return colordata;
}
private function toARGB(a:int, r:int, g:int, b:int):uint
{
return a << 25 | r << 16 | g << 8 | b;
}
}
/**
* ...
* @author
*/
class PaletteMapColorData
{
public var r:Array = new Array();
public var g:Array = new Array();
public var b:Array = new Array();
}
/**
* ...
* @author
*/
class Image
{
public static const IMAGE_W:Number = 350; // 基本サイズ
public static const IMAGE_H:Number = 200; // 基本サイズ
public var width:Number;
public var height:Number;
private var baseimg:BitmapData;
public var img:BitmapData = null;
public var bmp:Bitmap;
public var view:Sprite;
private var _shadowShape:Shape;
public function Image( path:String )
{
width = IMAGE_W;
height = IMAGE_H;
var loader:ImageLoader = new ImageLoader();
loader.Load( path, LoadedImage );
bmp = new Bitmap();
_shadowShape = new Shape();
view = new Sprite();
view.addChild( _shadowShape );
view.addChild( bmp );
}
private function LoadedImage( imgloader:ImageLoader ) : void
{
var loader:Loader = imgloader.img;
var scaleX:Number = Math.min( width / loader.width, 1 );
var scaleY:Number = Math.min( height / loader.height, 1 );
var scale:Number;
if ( scaleX < scaleY ) scale = scaleX;
else scale = scaleY;
var scaleW:Number = loader.width * scale;
var scaleH:Number = loader.height * scale;
// 画像の広さを更新する
width = scaleW;
height = scaleH;
baseimg = new BitmapData( width, height, true, 0 );
baseimg.draw( loader, new Matrix( scale, 0, 0, scale, 0, 0 ) );
img = baseimg.clone();
bmp.bitmapData = img;
bmp.x = -width / 2;
bmp.y = -height / 2;
CreateShadow();
}
public function UpdatePaletteMap( map:PaletteMapColorData ) : void
{
if ( img != null )
{
img.paletteMap(baseimg, img.rect, new Point(), map.r, map.g, map.b);
}
}
private function CreateShadow() : void
{
var g:Graphics = _shadowShape.graphics;
g.beginFill(0);
g.drawRect( 0, 0, width, height );
g.endFill();
_shadowShape.x = bmp.x;
_shadowShape.y = bmp.y;
_shadowShape.filters = [new BlurFilter(10,10,1)];
}
}
/**
* ...
* @author
*/
class ImageLoader
{
public var img:Loader = null;
private var _loadinfo:LoaderInfo = null;
private var _completecallback:Function = null;
public function Load( path:String, callback:Function ) : void
{
img = new Loader();
_loadinfo = img.contentLoaderInfo;
_loadinfo.addEventListener(Event.COMPLETE,LoadCompleteListener);
_completecallback = callback;
var url:URLRequest = new URLRequest( path );
img.load( url );
}
private function LoadCompleteListener( e:Event ) : void
{
_loadinfo.removeEventListener( Event.COMPLETE, LoadCompleteListener );
if ( _completecallback != null ) _completecallback( this );
_completecallback = null;
}
}