forked from: 文字のアウトライン抽出(処理最適化)
1. 左上の入力欄に、好きな文字を一文字入力
2. パラメータで激しさ変更
3. Clickで静止
4. 右上チェックボックスでパス表示
※少しづつバグを修正
■ 文字内のパスの色を白に
■ パスの取得方法
【修正】
■ adjustmentPath:パスの最適化
■ エッジ取得方法(簡素化):2値化追加、余分な処理削除
【参考】
http://jsdo.it/tsmallfield/stars2
http://wonderfl.net/c/1uJu/read
/**
* Copyright 53able ( http://wonderfl.net/user/53able )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/7IKR
*/
// forked from enok's 文字のアウトライン抽出(処理最適化)
/*
1. 左上の入力欄に、好きな文字を一文字入力
2. パラメータで激しさ変更
3. Clickで静止
4. 右上チェックボックスでパス表示
※少しづつバグを修正
■ 文字内のパスの色を白に
■ パスの取得方法
【修正】
■ adjustmentPath:パスの最適化
■ エッジ取得方法(簡素化):2値化追加、余分な処理削除
【参考】
http://jsdo.it/tsmallfield/stars2
http://wonderfl.net/c/1uJu/read
*/
package {
import com.bit101.components.CheckBox;
import com.bit101.components.HUISlider;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFieldType;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
[SWF(width="465", height="465", frameRate="48")]
public class Index extends Sprite {
private static var DEBUG:Boolean = false;
private static var DEFAULT_TEXT:String = "Zynga\nJapan";
private static var TEXT_INPUT_AREA_H:Number = 30;
private var _updateFlg:Boolean = true; //描画フラグ
private var _fontPlay:FontPlay; //文字解析クラス
private var _pathBitmap:Bitmap; //パスのビットマップ
private var _clickArea:Sprite; //クリックの範囲
private var _pathArea:Sprite; //パスの描画領域
private var _pathViewArea:Sprite = new Sprite(); //パスの描画領域
private var _viewTextArea:Sprite = new Sprite(); //デバッグ用
private var _basePathLists:Array = []; //パスの元位置
private var _updatePathLists:Array = []; //パスの現位置
private var _param1:Number = 1500;
public function Index():void {
trace("Class : Index");
stage ? init() : addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init():void {
trace("Method : init");
//Wonderfl.capture_delay(10);
Wonderfl.disable_capture();
createInputTextArea(); //文字入力エリア生成
if(DEBUG){ createViewTextArea(); }
createFontPlay(); //文字解析クラス生成
createEdge(); //エッジ生成
createPath(); //パス生成
setPathList(); //パスリスト初期化
createController(); //コントローラ生成
createClickArea(); //クリックエリア設定
addEventListener(Event.ENTER_FRAME, update);
_clickArea.addEventListener(MouseEvent.CLICK, click);
}
private function update(e:Event = null):void {
//この関数を変更すると動きを変えられる
var point:Point;
var x:Number, y:Number, ax:Number, ay:Number, mouseXL:Number, mouseYL:Number, theta:Number, dist:Number;
var mouseDiffX:Number = (stage.stageWidth / 2 - _fontPlay.textField.width / 2);
var mouseDiffY:Number = (stage.stageHeight / 2 - _fontPlay.textField.height / 2 + TEXT_INPUT_AREA_H);
for (var i:uint=0; i < _updatePathLists.length; i++ ) {
for(var j:uint=0; j < _updatePathLists[i].length; j++ ) {
x = _updatePathLists[i][j].x;
y = _updatePathLists[i][j].y;
mouseXL = mouseX - mouseDiffX;
mouseYL = mouseY - mouseDiffY;
theta = Math.atan2(y - mouseYL, x - mouseXL);
dist = _param1 / Math.sqrt( ( mouseXL - x ) * ( mouseXL - x ) + ( mouseYL - y ) * ( mouseYL - y ) );
dist = dist > 10 ? 4 : dist;
ax = (Math.cos(theta) * dist + (_basePathLists[i][j].x - x) * 0.15) * 0.4;
ay = (Math.sin(theta) * dist + (_basePathLists[i][j].y - y) * 0.15) * 0.4
_updatePathLists[i][j].x += ax;
_updatePathLists[i][j].y += ay;
}
}
_fontPlay.drawLines(_updatePathLists);
}
private function click(e:MouseEvent):void {
if(_updateFlg){
removeEventListener(Event.ENTER_FRAME, update);
_updateFlg = false;
}else {
addEventListener(Event.ENTER_FRAME, update);
_updateFlg = true;
}
}
private function createInputTextArea():void {
trace("Method : createInputTextArea");
//Config
var textFieldH:Number = 20, textFieldW:Number = 25;
//BackGround
var textInputArea:Sprite = new Sprite();
textInputArea.graphics.beginFill(0xdddddd, 1);
textInputArea.graphics.drawRect(0, 0, stage.stageWidth, TEXT_INPUT_AREA_H);
textInputArea.graphics.endFill();
addChild(textInputArea);
//TextFild
var textField:TextField = new TextField();
textField.defaultTextFormat = new TextFormat("小塚ゴシック Pro L", 14, 0x000000, true,null,null,null,null,TextFormatAlign.CENTER);
textField.x = textField.y = (TEXT_INPUT_AREA_H - textFieldH) / 2;
textField.width = textFieldW;
textField.height = textFieldH;
textField.type = TextFieldType.INPUT;
textField.maxChars = 1;
textField.border = textField.background = true;
textField.borderColor = 0x222222;
textField.backgroundColor = 0xffffff;
textField.text = DEFAULT_TEXT;
addChild(textField);
//Event
textField.addEventListener(Event.CHANGE, function(e:Event):void { onChange(textField.text, e); } );
}
private function onChange(text:String, e:Event=null):void {
trace("Method : onChange");
if(text){
_fontPlay.change(text);
createEdge();
createPath();
setPathList();
}
}
private function createViewTextArea():void {
//debug
trace("Method : createViewTextArea");
_viewTextArea.y = TEXT_INPUT_AREA_H;
_viewTextArea.graphics.beginFill(0xffffff);
_viewTextArea.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight-TEXT_INPUT_AREA_H);
_viewTextArea.graphics.endFill();
_viewTextArea.width = stage.stageWidth;
_viewTextArea.height = stage.stageHeight - TEXT_INPUT_AREA_H;
addChild(_viewTextArea);
}
private function createFontPlay():void {
trace("Method : createFontPlay");
_fontPlay = new FontPlay(DEFAULT_TEXT);
//debug
if(DEBUG){
var textField:TextField = _fontPlay.textField;
textField.x = stage.stageWidth / 2 - textField.width / 2;
textField.y = stage.stageHeight / 2 - textField.height / 2;
_viewTextArea.addChild(textField);
}
}
private function createEdge():void {
trace("Method : createEdge");
if (_pathBitmap) { _pathViewArea.removeChild(_pathBitmap); } else { addChild(_pathViewArea); }
_pathBitmap = new Bitmap(_fontPlay.pathBitmapData);
_pathBitmap.x = stage.stageWidth / 2 - _fontPlay.textField.width / 2;
_pathBitmap.y = stage.stageHeight / 2 - _fontPlay.textField.height / 2 + TEXT_INPUT_AREA_H;
_pathBitmap.alpha = 0.2;
_pathViewArea.addChild(_pathBitmap);
}
private function createPath():void {
trace("Method : createPath");
if (_pathArea) { _pathViewArea.removeChild(_pathArea); }
_pathArea = _fontPlay.pathArea;
_pathArea.x = stage.stageWidth / 2 - _fontPlay.textField.width / 2;
_pathArea.y = stage.stageHeight / 2 - _fontPlay.textField.height / 2 + TEXT_INPUT_AREA_H;
_pathViewArea.addChild(_pathArea);
}
private function setPathList():void {
_basePathLists = _fontPlay.basePointLists.concat();
_updatePathLists = [];
for (var i:uint = 0; i < _basePathLists.length; i++ ) {
_updatePathLists[i] = []
for (var j:uint = 0; j < _basePathLists[i].length; j++ ) {
_updatePathLists[i][j] = new Point(_basePathLists[i][j].x, _basePathLists[i][j].y);
}
}
}
private function createClickArea():void {
//Click Area
_clickArea = new Sprite();
_clickArea.graphics.beginFill(0x000000, 0);
_clickArea.graphics.drawRect(0, TEXT_INPUT_AREA_H, stage.stageWidth, stage.stageHeight - TEXT_INPUT_AREA_H);
_clickArea.graphics.endFill();
addChild(_clickArea);
}
private function createController():void {
var slider:HUISlider = new HUISlider(this, 40, 7, "Param1", onSlide1);
slider.width = 420;
slider.minimum = 0;
slider.maximum = 2000;
slider.value = _param1;
slider.labelPrecision = 0;
var checkBox:CheckBox = new CheckBox(this, 442, 11, "", onCheck);
}
private function onSlide1(e:Event):void {
_param1 = e.target.value;
}
private function onCheck(e:Event):void {
_fontPlay.viewPathFlg = e.target.selected;
}
}
}
import flash.display.BitmapData;
import flash.display.GraphicsPathCommand;
import flash.display.GraphicsPathWinding;
import flash.display.Sprite;
import flash.filters.ConvolutionFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
class FontPlay {
private var _text:String= "";
private var _textField:TextField = new TextField();
private var _pathArea:Sprite = new Sprite();
private var _bitmapData:BitmapData;
private var _pathBitmapData:BitmapData;
private var _edgeList:Array = [];
private var _commandsLists:Array = [];
private var _basePointLists:Array = [];
private var _pathLists:Array = [];
private var _viewPathFlg:Boolean = false;
public function FontPlay(string:String):void {
trace("Class : FontPlay");
_text = string;
//解析
analyze(_text);
}
public function analyze(str:String):void {
trace("Method : analyze");
//TextFiled生成
createTextFiled(str);
//パスデータ生成
createPathLists();
}
private function createTextFiled(str:String):void {
trace("Method : createTextFiled");
//TextFild
_textField = new TextField();
_textField.defaultTextFormat = new TextFormat("小塚ゴシック Pro L", 127, 0x000000, true,null,null,null,null,TextFormatAlign.CENTER);
//_textField.defaultTextFormat = new TextFormat("", 127, 0x000000, true,null,null,null,null,TextFormatAlign.CENTER);
_textField.text = str;
_textField.autoSize = TextFieldAutoSize.LEFT;
_textField.scaleX = _textField.scaleY = 2;
}
private function createPathLists():void {
//文字のビットマップ生成
createBitmapData();
//ビットマップの解析
analyzeBitmapData();
//エッジのビットマップデータ生成
crateEdgeBitmapData(_edgeList);
//パスデータの生成
createPath(_edgeList);
}
private function createBitmapData():void {
trace("Method : createBitmapData");
var scale:Number = 2.0;
var matrix:Matrix = new Matrix(scale, 0, 0, scale);
_bitmapData = new BitmapData(_textField.width*scale, _textField.height*scale, true);
_bitmapData.draw(_textField, matrix);
//2値化
_bitmapData.threshold(_bitmapData, new Rectangle(0, 0, _bitmapData.width, _bitmapData.height), new Point(0, 0), "<", 0x00ffffff, 0xff000000, 255, false);
}
private function analyzeBitmapData():void {
trace("Method : analyzeBitmapData");
_edgeList = [];
var edgeBitmapData:BitmapData = edgeDetection(_bitmapData);
var color:uint;
for (var x:uint = 0; x < edgeBitmapData.width; x++ ) {
_edgeList[x] = [];
for (var y:uint = 0; y < edgeBitmapData.height; y++ ) {
color = edgeBitmapData.getPixel(x, y);
if (color != 0x000000) {
_edgeList[x][y] = 0x000000;
}else {
_edgeList[x][y] = 0xffffff;
}
}
}
}
private function edgeDetection(bitmapData:BitmapData):BitmapData {
//エッジ抽出
var edgeBitmapData:BitmapData = new BitmapData(bitmapData.width, bitmapData.height, true);
var cf1:ConvolutionFilter = new ConvolutionFilter(3, 3, [ 0, 1, 0, 1, -4, 1, 0, 1, 0]);
//var cf2:ConvolutionFilter = new ConvolutionFilter(3, 3, [ -1, -1, -1, -1, 8, -1, -1, -1, -1]);
edgeBitmapData.applyFilter(bitmapData, bitmapData.rect, new Point(), cf1);
//edgeBitmapData.applyFilter(edgeBitmapData, edgeBitmapData.rect, new Point(), cf2);
return edgeBitmapData;
}
private function crateEdgeBitmapData(edgeList:Array):void {
trace("Method : cratePathList");
_pathBitmapData = new BitmapData(_bitmapData.width, _bitmapData.height, false);
_pathBitmapData.lock();
for (var x:uint = 0; x < edgeList.length; x++ ) {
for (var y:uint = 0; y < edgeList[x].length; y++ ) {
_pathBitmapData.setPixel(x, y, edgeList[x][y]);
}
}
_pathBitmapData.unlock();
}
private function createPath(edgeList:Array):void {
trace("Method : createPath");
var count:uint = 0;
_pathLists = [];
_pathLists[count] = [];
var currentEdge:Point = getFirstPath(edgeList);
if (currentEdge) {
_pathLists[count].push(currentEdge);
}
while(true){
var nextPoint:Point = getNext(edgeList, currentEdge);
if (nextPoint) {
_pathLists[count].push(nextPoint);
edgeList[nextPoint.x][nextPoint.y] = 0xffffff;
currentEdge = nextPoint;
}else {
currentEdge = getFirstPath(edgeList);
if (currentEdge) {
count++;
_pathLists[count] = [];
_pathLists[count].push(currentEdge);
//break;
}else {
break;
}
}
}
//パスデータの調整
_pathLists = adjustmentPath(_pathLists, 0.7);
_basePointLists = _pathLists;
drawLines(_pathLists);
}
private function adjustmentPath(pathLists:Array, threshold:Number=0):Array {
trace("Method : adjustmentPath");
var newList:Array = [];
var preList:Array = [];
var preAngle:Number, nowAngle:Number;
for (var i:uint = 0; i < pathLists.length; i++ ) {
if (pathLists[i].length < 3) {
newList[i] = pathLists[i];
continue;
}
newList[i] = [];
preList = [];
preList[0] = pathLists[i][0];
preList[1] = pathLists[i][1];
preAngle = Math.atan2(preList[0].y - preList[1].y, preList[0].x - preList[1].x);
newList[i].push(pathLists[i][0]);
for (var j:uint = 1; j < pathLists[i].length; j++ ) {
var p:Point;
if (j == pathLists[i].length - 1) {
nowAngle = Math.atan2(pathLists[i][0].y-pathLists[i][j].y, pathLists[i][0].x-pathLists[i][j].x);
p = pathLists[i][0];
}else{
nowAngle = Math.atan2(pathLists[i][j].y - pathLists[i][j+1].y, pathLists[i][j].x - pathLists[i][j+1].x);
p = pathLists[i][j + 1];
}
//角度が大きいPathを残す
//斜め線の処理追加予定
if (Math.abs(Math.abs(nowAngle) - Math.abs(preAngle)) > threshold) {
newList[i].push(p);
preAngle = Math.abs(nowAngle);
}
}
}
return newList;
}
//開始点を取得
private function getFirstPath(edgeList:Array):Point {
var firstEdge:Point;
out :for (var x:uint = 0; x < edgeList.length; x++ ) {
for (var y:uint = 0; y < edgeList[x].length; y++ ) {
if (edgeList[x][y] == 0x000000) {
firstEdge = new Point(x, y);
edgeList[x][y] = 0xffffff;
break out;
}
}
}
return firstEdge;
}
//次の点を取得
private function getNext(edgeList:Array, currentPoint:Point):Point {
//エッジ抽出の精度が悪いので、それなりの範囲を調査
var nextPoint:Point;
var x:uint = currentPoint.x;
var y:uint = currentPoint.y;
if(edgeList[x - 1][y - 1]==0x000000){
nextPoint = new Point(x - 1, y - 1);
}else if (edgeList[x][y - 1]==0x000000) {
nextPoint = new Point(x, y - 1);
}else if (edgeList[x + 1][y - 1]==0x000000) {
nextPoint = new Point(x + 1, y - 1);
}else if (edgeList[x + 1][y]==0x000000) {
nextPoint = new Point(x + 1, y);
}else if (edgeList[x + 1][y + 1] == 0x000000) {
nextPoint = new Point(x + 1, y + 1);
}else if (edgeList[x][y + 1]==0x000000) {
nextPoint = new Point(x, y + 1);
}else if (edgeList[x - 1][y + 1]==0x000000) {
nextPoint = new Point(x - 1, y + 1);
}else if (edgeList[x - 1][y] == 0x000000) {
nextPoint = new Point(x - 1, y);
}
return nextPoint;
}
//複数パスの線の描画
public function drawLines(pathLists:Array):void {
_pathArea.graphics.clear();
for (var i:uint = 0; i < pathLists.length; i++ ){
var datas:Array = createGraphicCommands(pathLists[i]);
drawLine(datas);
}
}
//GraphicCommandsを設定
private function createGraphicCommands(pathList:Array):Array {
_commandsLists = [];
var commands:Vector.<int> = new Vector.<int>();
var anchor:Vector.<Number> = new Vector.<Number>();
for (var i:uint = 0; i < pathList.length; i++ ) {
anchor.push(pathList[i].x, pathList[i].y);
if (i == 0) {
commands[0] = GraphicsPathCommand.MOVE_TO;
}else {
commands[i] = GraphicsPathCommand.LINE_TO;
}
}
anchor.push(pathList[0].x, pathList[0].y);
commands[i] = GraphicsPathCommand.LINE_TO;
_commandsLists[0] = commands;
_commandsLists[1] = anchor;
return _commandsLists;
}
//線の描画
private function drawLine(datas:Array):void {
_pathArea.graphics.beginFill(0x000000, 1);
_pathArea.graphics.lineStyle(1, 0xffffff, 0.8);
_pathArea.graphics.drawPath(datas[0], datas[1], GraphicsPathWinding.NON_ZERO);
_pathArea.graphics.endFill();
_pathArea.graphics.lineStyle();
if(_viewPathFlg){
var radius:Number = 2;
for (var i:uint = 0; i < datas[1].length; i += 2) {
_pathArea.graphics.beginFill(0xdd4422, 1.0);
_pathArea.graphics.drawCircle( datas[1][i]-radius/2, datas[1][i+1]-radius/2, radius);
_pathArea.graphics.endFill();
}
}
}
//文字変更
public function change(str:String):void {
analyze(str);
}
public function get textField():TextField { return _textField; }
public function get pathBitmapData():BitmapData { return _pathBitmapData; }
public function get pathArea():Sprite { return _pathArea; }
public function get commandsLists():Array { return _commandsLists; }
public function get basePointLists():Array { return _basePointLists; }
public function set viewPathFlg(value:Boolean):void { _viewPathFlg = value; }
}