Icon Editor (under construction)
================================================================
* アイコンエディタのようなものを作り中
* ================================================================
* To do:
* 4. ドラッグで描いたり塗りつぶしたりできるようにする
* 5. 用途により 32x32ピクセルでは大きすぎることがあるので
* 16x16や24x24でも作れるようにしたい
* 6. アルファチャンネルあるいは少なくとも透過ビットをサポートしたい
* ================================================================
* 変更履歴
* ================================================================
* 2010年4月13日
* 変更点:
* ◆それなりにUndoをサポート:
* ピクセル値の変更とパレット色の変更が取り消し可能.
* さしあたって5回まで遡れるようにした.
* (Fix Me: Redo がない. データ退避/復帰の効率が悪い.)
* ◆ボタンをクラス化
* ◆変更履歴は新しいのを上にした
* ----------------------------------------------------------------
* 2010年4月12日
* 変更点:
* ◆パレットの書き替え:
* テキストフィールドの数値をいじって色を変更し, その右の矩形 (カラープレビュー)
* をクリックしたら, 現在選択されているパレットの色が置換され, 編集中のデータの
* 表示にも反映される...
* ◆[COPY]ボタンをつけた:
* スタティックテキスト [COPY] をクリックすると, システムのクリップボードに
* 現在のアイコンデータが XPM 形式で転送される. これをテキストとしてペースト
* して 拡張子 xpm をつけて保存することで, GIMP などで開ける画像ファイルとなる.
* [COPY]ボタンの反転表示は, 画像データが変更されたら普通の(白地に黒文字の)
/**
* Copyright tenasaku ( http://wonderfl.net/user/tenasaku )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/9Ihd
*/
/* ================================================================
* アイコンエディタのようなものを作り中
* ================================================================
* To do:
* 4. ドラッグで描いたり塗りつぶしたりできるようにする
* 5. 用途により 32x32ピクセルでは大きすぎることがあるので
* 16x16や24x24でも作れるようにしたい
* 6. アルファチャンネルあるいは少なくとも透過ビットをサポートしたい
* ================================================================
* 変更履歴
* ================================================================
* 2010年4月13日
* 変更点:
* ◆それなりにUndoをサポート:
* ピクセル値の変更とパレット色の変更が取り消し可能.
* さしあたって5回まで遡れるようにした.
* (Fix Me: Redo がない. データ退避/復帰の効率が悪い.)
* ◆ボタンをクラス化
* ◆変更履歴は新しいのを上にした
* ----------------------------------------------------------------
* 2010年4月12日
* 変更点:
* ◆パレットの書き替え:
* テキストフィールドの数値をいじって色を変更し, その右の矩形 (カラープレビュー)
* をクリックしたら, 現在選択されているパレットの色が置換され, 編集中のデータの
* 表示にも反映される...
* ◆[COPY]ボタンをつけた:
* スタティックテキスト [COPY] をクリックすると, システムのクリップボードに
* 現在のアイコンデータが XPM 形式で転送される. これをテキストとしてペースト
* して 拡張子 xpm をつけて保存することで, GIMP などで開ける画像ファイルとなる.
* [COPY]ボタンの反転表示は, 画像データが変更されたら普通の(白地に黒文字の)
* 表示に戻る.
* ----------------------------------------------------------------
* 2010年4月11日
* ◆アイコンエディタのようなものを作り中 (着手)
* ----------------------------------------------------------------
*/
package {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
import flash.system.*;
import flash.text.*;
public class FlashTest extends Sprite {
private const NUM_COLS:int = 32;
private const NUM_ROWS:int = 32;
private const NUM_UNDOABLE:int = 5;
private const FAT_BIT_SIZE:int = 12;
private const COLORPREVIEW_RECT:Rectangle = new Rectangle(0,0,32,20);
private const DEFAULT_PALETTE:Array = [
0xffffff,
0xaaaaaa,
0x555555,
0x000000,
0x800000,
0xff0000,
0xff00ff,
0x800080,
0x000080,
0x0000ff,
0x00ccff,
0x008080,
0x008000,
0x00ff00,
0xffff00,
0xc08040
];
private var palette:Array;
private var textRVal:TextField;
private var textGVal:TextField;
private var textBVal:TextField;
private var colorPreview:Bitmap;
private var colorPreviewBMD:BitmapData;
private var iconPreview:Bitmap;
private var iconPreviewBMD:BitmapData;
private var fatBits:Bitmap;
private var fatBitsBMD:BitmapData;
private var iconData:Array;
private var selectedPaletteNum:int;
private var outputField:TextField;
private var copyButton:MyButton;
private var undoButton:MyButton;
private var undoBuffer:Array;
// アンドゥ用にデータを退避
private function pushUndoBuffer():void {
var undoData:Array = new Array();
// その時点でのデータ配列の全要素の数値を別の配列に書き写してバッファに格納する
// (もとの配列自体をバッファに入れてもうまく行かなんだ)
var i:int;
for ( i = 0 ; i < palette.length ; ++i ) {
undoData.push(palette[i]);
}
for ( i = 0 ; i < iconData.length ; ++ i ) {
undoData.push(iconData[i]);
}
undoBuffer.push(undoData);
while ( undoBuffer.length > NUM_UNDOABLE ) {
undoBuffer.shift();
}
undoButton.hilit = ( undoBuffer.length > 0 );
}
// アンドゥ・バッファに退避したデータを復帰
private function popUndoBuffer():void {
if ( undoBuffer.length == 0 ) return;
var undoData:Array = undoBuffer.pop();
var i:int;
for ( i = 0 ; i < palette.length ; ++i ) {
palette[i] = undoData.shift();
}
for ( i = 0 ; i < iconData.length ; ++i ) {
iconData[i] = undoData.shift();
}
redrawIconImages();
redrawPalette();
copyButton.hilit = false;
undoButton.hilit = ( undoBuffer.length > 0 );
}
private function storeXpm():void {
const DQ:String = "\x22"; // double quotation
const HT:String = "\t";
const COMMA:String = "\,";
const LF:String = "\n";
const HEXDIGIT:String = "0123456789abcdef";
var s:String = "/* XPM */\nstatic char * _IconEditor_output [] = {\n";
s += "\t/* values */\n\t "+DQ+"32 32 17 1"+DQ+",\n\t/* colors */\n";
var p:int;
for ( p = 0 ; p < 16 ; ++p ) {
var c:uint = palette[p*2+1];
var rgb:Number = new Number(0x1000000+c);
s += HT+DQ+HEXDIGIT.charAt(p)+" c #"+rgb.toString(16).substr(1)+DQ+COMMA+LF;
}
s += HT+DQ+" c None"+DQ+COMMA+LF+HT+"/* pixels */" + LF;
var col:int,row:int;
for ( row = 0 ; row < NUM_ROWS ; ++row ) {
s += HT + DQ;
for ( col = 0 ; col < NUM_COLS ; ++col ) {
s += HEXDIGIT.charAt(iconData[row*NUM_COLS+col]);
}
s += DQ;
if ( row + 1 < NUM_ROWS ) { s += COMMA; }
s += LF;
}
s += "};" + LF;
outputField.text = s;
}
private function outputFieldInit():void {
var fmt:TextFormat = new TextFormat();
fmt.size = 9;
fmt.font = "Courier";
fmt.align = TextFormatAlign.LEFT;
fmt.leftMargin = 2;
outputField.width = stage.stageWidth;
outputField.height = stage.stageHeight;
outputField.selectable = true;
outputField.background = true;
outputField.backgroundColor = 0x000080;
outputField.textColor = 0xffff00;
outputField.defaultTextFormat = fmt;
outputField.visible = false;
}
// ひとつのドットだけ現在の値で再描画
private function drawIconDot(col:int,row:int):void {
if ( ( col < 0 )||( col >= NUM_COLS ) ) return;
if ( ( row < 0 )||( row >= NUM_ROWS ) ) return;
var p:int = iconData[row*NUM_COLS+col];
fatBitsBMD.fillRect(
new Rectangle(
col*FAT_BIT_SIZE+1,
row*FAT_BIT_SIZE+1,
FAT_BIT_SIZE-1,
FAT_BIT_SIZE-1
),
palette[p*2+1]
);
iconPreviewBMD.setPixel(col,row,palette[p*2+1]);
}
// FatBitsグリッドとプレビュー全体を現在の値で再描画
private function redrawIconImages():void {
fatBitsInit();
var row:int,col:int;
for ( row = 0 ; row < NUM_ROWS ; ++row )
for ( col = 0 ; col < NUM_COLS ; ++col ) {
drawIconDot(row,col);
}
}
// パレットの表示を更新する
private function redrawPalette():void {
var i:int;
for ( i = 0 ; i < 16 ; ++i ) {
palette[i*2].graphics.clear();
if ( i == selectedPaletteNum ) {
palette[i*2].graphics.lineStyle(3,0x000000);
palette[i*2].graphics.beginFill(palette[i*2+1]);
palette[i*2].graphics.drawRect(0,0,32,20);
palette[i*2].graphics.endFill();
palette[i*2].graphics.lineStyle(0,0xfffffff);
palette[i*2].graphics.drawRect(1,1,30,18);
} else {
palette[i*2].graphics.lineStyle(1,0x999999);
palette[i*2].graphics.beginFill(palette[i*2+1]);
palette[i*2].graphics.drawRect(0,0,32,20);
palette[i*2].graphics.endFill();
}
}
}
// パレット番号を指定し以後の描画にその色を使う
private function selectPaletteColor(_c:int):void {
selectedPaletteNum = _c;
var cc:uint = palette[_c*2+1];
textRVal.text = String(0xff&(cc>>16));
textGVal.text = String(0xff&(cc>>8));
textBVal.text = String(0xff&cc);
redrawPalette();
colorUpdate(null);
}
// 現在選択されているパレットの値を
// カラー・プレビューに表示されている値に変更する
private function paletteChange():void {
pushUndoBuffer();
colorUpdate(null);
palette[selectedPaletteNum*2+1]
= colorPreviewBMD.getPixel(
Math.floor(colorPreviewBMD.width/2),
Math.floor(colorPreviewBMD.height/2)
);
copyButton.hilit = false;
redrawPalette();
redrawIconImages();
}
// カラーパレットの初期化 (デフォルト値にする)
private function paletteInit():void {
var i:int;
for ( i = 0 ; i < 16 ; ++i ) {
palette[i*2] = new Shape();
palette[i*2].graphics.clear();
palette[i*2].graphics.lineStyle(1,0x999999);
palette[i*2].graphics.beginFill(DEFAULT_PALETTE[i]);
palette[i*2].graphics.drawRect(0,0,32,20);
palette[i*2].graphics.endFill();
palette[i*2].x = 420;
palette[i*2].y = 24*i +48;
palette[i*2+1] = DEFAULT_PALETTE[i];
this.addChild(palette[i*2]);
}
}
// FatBitsグリッドの初期化
private function fatBitsInit():void {
fatBitsBMD.fillRect(
new Rectangle(0,0,FAT_BIT_SIZE*NUM_COLS+1,FAT_BIT_SIZE*NUM_ROWS+1),
0xffffff
);
var i:int,j:int;
for ( i = 0 ; i <= 32 ; ++i ) {
for ( j = 0 ; j <= 32*FAT_BIT_SIZE ; ++j ) {
fatBitsBMD.setPixel(j,i*FAT_BIT_SIZE,0x999999);
fatBitsBMD.setPixel(i*FAT_BIT_SIZE, j,0x999999);
}
}
}
// カラー入力フィールドの変更に対処
private function colorUpdate(e:Event):void {
var r:int = int(textRVal.text);
var g:int = int(textGVal.text);
var b:int = int(textBVal.text);
r = Math.max(0,Math.min(r,255));
g = Math.max(0,Math.min(g,255));
b = Math.max(0,Math.min(b,255));
textRVal.text = r.toString();
textGVal.text = g.toString();
textBVal.text = b.toString();
var cc:uint = (r<<16)|(g<<8)|b;
colorPreviewBMD.fillRect(COLORPREVIEW_RECT, cc);
}
// カラー入力フィールドの初期設定
private function colorValFieldInit():void {
var fmt:TextFormat = new TextFormat();
fmt.size = 14;
fmt.align = TextFormatAlign.RIGHT;
fmt.font = "Courier";
for each (var tf:TextField in [ textRVal, textGVal, textBVal ] ) {
tf.type = TextFieldType.INPUT;
tf.restrict = "0-9";
tf.multiline = false;
tf.y = 20;
tf.width = 30;
tf.height = 20;
tf.background = true;
tf.border = true;
tf.borderColor = 0xcccc99;
tf.defaultTextFormat = fmt;
tf.text = "0";
}
textRVal.x = 30;
textGVal.x = 72;
textBVal.x = 114;
}
// クリックへの対処
private function onClick(e:MouseEvent):void {
var X:int,Y:int;
// パレットをクリック?
if (e.stageX >= 420 ) {
X = e.stageX - 420;
if ( ( X < 0 )||( X >= 32 ) ) return;
Y = e.stageY - 48;
if ( ( Y < 0 )||( Y >= 16*23+20) ) return;
var _c:int = Y/24;
var _r:int = Y%24;
if ( _r >= 20 ) return;
selectPaletteColor(_c);
return;
}
// COPY ボタンをクリック?
if ( copyButton.includesPoint(e.stageX,e.stageY) ) {
storeXpm();
System.setClipboard(outputField.text);
copyButton.hilit = true;
return;
}
// UNDO ボタンをクリック?
if ( undoButton.includesPoint(e.stageX,e.stageY) ) {
// アンドゥを実行
popUndoBuffer();
}
// カラー・プレビューをクリック?
X = e.stageX - colorPreview.x;
Y = e.stageY - colorPreview.y;
if ( ( X >= 0 )&&( X < colorPreview.width )
&&( Y >= 0 )&&( Y < colorPreview.height ) ) {
paletteChange();
}
// FatBitsグリッドをクリック?
X = e.stageX - fatBits.x;
Y = e.stageY - fatBits.y;
var _col:int = Math.floor(X/FAT_BIT_SIZE);
var _row:int = Math.floor(Y/FAT_BIT_SIZE);
if ( ( _col < 0 )||( _col >= 32 ) ) return;
if ( ( _row < 0 )||( _row >= 32 ) ) return;
pushUndoBuffer();
copyButton.hilit = false;
var p:int = iconData[_row*NUM_COLS+_col];
if ( p == selectedPaletteNum ) {
iconData[_row*NUM_COLS+_col] = 0;
drawIconDot(_col,_row);
} else {
iconData[_row*NUM_COLS+_col] = selectedPaletteNum;
drawIconDot(_col,_row);
}
}
// 全体の初期化
private function initialize(e:Event):void {
this.removeEventListener(Event.ADDED_TO_STAGE, initialize);
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
undoBuffer = new Array();
iconData = new Array();
while ( iconData.length < NUM_COLS*NUM_ROWS ) {
iconData.push(0);
}
this.graphics.clear();
this.graphics.beginFill(0xffffff);
this.graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);
this.graphics.endFill();
// 色指定テキストフィールドの設置
textRVal = new TextField();
textGVal = new TextField();
textBVal = new TextField();
colorValFieldInit();
this.addChild(textRVal);
this.addChild(textGVal);
this.addChild(textBVal);
// カラー・プレビューの設置
colorPreviewBMD = new BitmapData(30,20,false,DEFAULT_PALETTE[0]);
colorPreview = new Bitmap(colorPreviewBMD);
colorPreview.x = 156;
colorPreview.y = 20;
this.graphics.lineStyle(1,0x999999);
this.graphics.drawRect(
colorPreview.x-1,
colorPreview.y-1,
colorPreview.width+1,
colorPreview.height+1
);
this.addChild(colorPreview);
// アイコンイメージのプレビューを配置
iconPreviewBMD = new BitmapData(32,32,false,0xffffff)
iconPreview = new Bitmap(iconPreviewBMD);
iconPreview.x = 420;
iconPreview.y = 10;
this.addChild(iconPreview);
this.graphics.lineStyle(1,0x999999);
this.graphics.drawRect(
iconPreview.x-1, iconPreview.y-1,
iconPreview.width+1,iconPreview.height+1
);
// [UNDO]ボタンの配置
undoButton = new MyButton();
undoButton.text = "UNDO";
undoButton.x = 198;
undoButton.y = 20;
this.addChild(undoButton);
// [COPY]ボタンの配置
copyButton = new MyButton();
copyButton.text = "COPY";
copyButton.x = 250;
copyButton.y = 20;
this.addChild(copyButton);
// パレットの初期設定
palette = new Array;
paletteInit();
selectPaletteColor(3);
// 編集用 FatBits グリッドの初期設定
fatBitsBMD = new BitmapData(FAT_BIT_SIZE*32+1,FAT_BIT_SIZE*32+1,false,0xffffff);
fatBitsInit();
fatBits = new Bitmap(fatBitsBMD);
fatBits.x = 20;
fatBits.y = 48;
this.addChild(fatBits);
// 出力データ用テキストフィールド(デフォルトで非表示)の配置
outputField = new TextField();
outputFieldInit();
this.addChild(outputField);
// テキストの変更を監視
for each (var tf:TextField in [ textRVal, textGVal, textBVal ] ) {
tf.addEventListener(Event.CHANGE, colorUpdate);
}
// クリックを監視(プログラム本体の処理開始)
stage.addEventListener(MouseEvent.CLICK, onClick);
}
public function FlashTest() {
if (stage != null) {
initialize(null);
} else {
this.addEventListener(Event.ADDED_TO_STAGE, initialize);
}
} // end of the constructor method
} // end of class
} // end of package
import flash.text.*;
class MyButton extends TextField {
private var _enabled:Boolean;
private var _hilit:Boolean;
// グローバル座標 (X, Y) がこのボタン上の位置であるかどうか判定
public function includesPoint(X:Number, Y:Number):Boolean {
return (
( X >= this.x )
&& ( X < this.x + this.width )
&& ( Y >= this.y )
&& ( Y < this.y + this.height )
);
}
// ハイライト表示にする/通常表示に戻す
public function set hilit( hi:Boolean ):void {
if ( !_enabled ) return;
_hilit = hi;
if ( hi ) {
this.backgroundColor = 0x000000;
this.textColor = 0xffffff;
} else {
this.backgroundColor = 0xffffff;
this.textColor = 0x000000;
}
}
public function get hilit():Boolean {
return _hilit;
}
/**
* 使用可能フラグを上げる/下げる
* 使用可能フラグを下げるとき表示をグレイアウトさせる
* ハイライト表示はキャンセルされる
*/
public function set enabled(v:Boolean):void {
_enabled = v;
if ( _enabled ) {
this.backgroundColor = 0xffffff;
this.textColor = 0x000000;
} else {
this.backgroundColor = 0xffffff;
this.textColor = 0x999999;
}
_hilit = false;
}
public function get enabled():Boolean {
return _enabled;
}
// constructor method ...
public function MyButton() {
var fmt:TextFormat = new TextFormat();
fmt.size = 12;
fmt.font = "Courier";
fmt.align = TextFormatAlign.CENTER;
this.selectable = false;
this.width = 40;
this.height = 20;
this.background = true;
this.backgroundColor = 0xffffff;
this.textColor = 0x000000;
this.border = true;
this.borderColor = 0x999999;
this.defaultTextFormat = fmt;
_hilit = false;
_enabled = true;
}
}