Pixelate by BitmapData#fillRect
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/wPwm
*/
package {
import com.bit101.components.PushButton;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.system.LoaderContext;
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#FFFFFF")]
/**
* Pixelate by BitmapData#fillRect
* Practice of strategy pattern
* @author Aquioux(Yoshida, Akio)
* @see http://aquioux.net/blog/?p=2617
* Picture 「写真素材 足成」 http://www.ashinari.com/
*/
public class Main extends Sprite {
// Pixelation 共用パラメータ
private const DEGREE:int = 5; // 減色の段階数
// Drawer 用パラメータ
private const INTERVAL:int = 9; // タイルの間隔
private const COLOR_SHIFT:Number = 0.075; // 表示色の変動値
// ビューアを塗りつぶす色
private const FILL_COLOR:uint = 0xFFFFFF;
// カンバス
private var canvas_:BitmapData;
// 前回押したボタン
private var prevButton_:PushButton;
// 描画クラス
private var drawer_:Drawer;
// 描画ふるまいクラス格納配列
private var behaviors_:Array;
// 描画データ
private var data_:Vector.<uint>;
// BitmapData.rect
private var sourceRect_:Rectangle;
// コンストラクタ
public function Main() {
var url:String = "http://assets.wonderfl.net/images/related_images/9/9c/9c61/9c61a1edd9101f41eefb3a5a40a4e4601c27e16cm";
var loader:Loader = new Loader();
loader.load(new URLRequest(url), new LoaderContext(true));
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
}
private function completeHandler(event:Event):void {
// ステージサイズ
var sw:uint = stage.stageWidth;
var sh:uint = stage.stageHeight;
// ソースイメージ
var loadedBmd:BitmapData = event.target.loader.content.bitmapData;
var sourceBmd:BitmapData = new BitmapData(52, 52);
sourceRect_ = sourceBmd.rect;
sourceBmd.draw(loadedBmd, new Matrix(52/200, 0, 0, 52/200));
// ビューアの生成
canvas_ = new BitmapData(sw, sh, false, FILL_COLOR);
addChild(new Bitmap(canvas_));
// ソースイメージの表示
addChild(new Bitmap(sourceBmd));
// ピクセル化クラス
Pixelation.degree = DEGREE;
data_ = Pixelation.pixelate(sourceBmd.clone());
// 描画クラス
drawer_ = new Drawer();
drawer_.interval = INTERVAL;
drawer_.colorShift = COLOR_SHIFT;
// 描画ふるまいクラス
behaviors_ = [
"Horizontal", new BehaviorHorizontal(),
"Vertival", new BehaviorVertical(),
"Alter 1", new BehaviorAlter1(),
"Alter 2", new BehaviorAlter2(),
"Square", new BehaviorSquare()
];
// 描画切り替えボタン
var buttonWidth:int = 50;
var buttonHeight:int = 20;
var len:int = behaviors_.length / 2;
for (var i:int = 0; i < len; i++) {
var button:PushButton = new PushButton(this, sw - (buttonWidth * (len - i)), 0, behaviors_[i * 2], buttonHandler);
button.width = buttonWidth;
button.height = buttonHeight;
button.name = String(i);
}
}
// ボタンハンドラ
private function buttonHandler(e:Event):void {
if (prevButton_) prevButton_.enabled = true;
var currentButton:PushButton = PushButton(e.target);
currentButton.enabled = false;
prevButton_ = currentButton;
drawer_.behavior = behaviors_[int(currentButton.name) * 2 + 1];
draw();
}
// 描画
private function draw():void {
// ビューアのクリア
canvas_.fillRect(canvas_.rect, FILL_COLOR);
// 描画実行
drawer_.draw(data_, canvas_, sourceRect_);
}
}
}
//package {
//import aquioux.display.bitmapDataEffector.GrayScale;
//import aquioux.display.bitmapDataEffector.Posterize;
//import aquioux.display.bitmapDataEffector.Smooth;
import flash.display.BitmapData;
import flash.filters.BitmapFilterQuality;
/**
* 入力された BitmapData のピクセル情報を取得して、青チャンネルから段階値を取得する
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class Pixelation {
/**
* 減色の段階数
*/
static public function set degree(value:int):void { _degree = value; }
static private var _degree:int = 5;
/**
* ポジ・ネガ反転
*/
static public function set invert(value:Boolean):void { _invert = value; }
static private var _invert:Boolean = false;
/**
* 段階値取得
* @param bmd 入力 BitmapData
* @return イメージ全 pixel の段階値を格納した配列
*/
static public function pixelate(bmd:BitmapData):Vector.<uint> {
// イメージ操作フィルター生成と入力 BitmapData への適用
// 平滑化
var smooth:Smooth = new Smooth();
smooth.strength = 1;
smooth.quality = BitmapFilterQuality.HIGH;
smooth.applyEffect(bmd);
// グレイスケール
new GrayScale().applyEffect(bmd);
// 減色
var posterize:Posterize = new Posterize();
posterize.degree = _degree + 1;
posterize.applyEffect(bmd);
// bmd.getVector
var pixelVector:Vector.<uint> = bmd.getVector(bmd.rect);
pixelVector.fixed = true;
// 減色の段階値を計算
var val:Number = 0xFF / _degree;
var len:uint = pixelVector.length;
var data:Vector.<uint> = new Vector.<uint>();
for (var i:int = 0; i < len; i++) {
var result:Number = (pixelVector[i] & 0xFF) / val;
if (result != int(result)) int(result + 1);
data.push(_invert ? result : _degree - result);
}
data.fixed = true;
return data;
}
}
//}
//package {
//import aquioux.display.colorUtil.CycleRGB;
import flash.display.BitmapData;
import flash.geom.Rectangle;
/**
* Pixelation で得られたデータを描画
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class Drawer {
/**
* タイルの間隔
*/
public function set interval(value:uint):void { _interval = value; }
private var _interval:int = 5;
/**
* 表示色の変動値
*/
public function set colorShift(value:Number):void { _colorShift = value; }
private var _colorShift:Number = 0.1;
/**
* 描画ふるまいクラス
*/
public function set behavior(value:IBehavior):void { _behavior = value; }
private var _behavior:IBehavior;
/**
* コンストラクタ
*/
public function Drawer() {
_behavior = new BehaviorSquare();
}
/**
* 描画
* @param data 表示のためのデータ
* @param canvas 表示カンバス
* @param rect 表示領域
*/
public function draw(data:Vector.<uint>, canvas:BitmapData, rect:Rectangle):void {
var width:uint = rect.width;
var angle:Number = Math.random() * 360;
var len:uint = data.length;
canvas.lock();
for (var i:int = 0; i < len; i++) {
var currentDegree:int = data[i] * 2 - 1;
if (currentDegree < 0) currentDegree = 0;
var posX:int = i % width;
var posY:int = (i / width) >> 0;
if (currentDegree > 0) canvas.fillRect(_behavior.execute(currentDegree, posX, posY, _interval), CycleRGB.getColor(angle));
angle += _colorShift;
}
canvas.unlock();
}
}
//}
//package {
import flash.geom.Rectangle;
/**
* 描画ふるまいクラスの interface
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ interface IBehavior {
/**
* ふるまいの実行(描画のための Rectangle を生成)
* @param degree 段階
* @param posX 開始X座標
* @param posY 開始Y座標
* @param interval タイルの間隔
* @return 描画のための Rectangle
*/
function execute(degree:int, posX:int, posY:int, interval:int):Rectangle;
}
//}
//package {
import flash.geom.Rectangle;
/**
* 描画ふるまいクラス(水平)
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class BehaviorHorizontal implements IBehavior {
/**
* ふるまいの実行(描画のための Rectangle を生成)
* @param degree 段階
* @param posX 開始X座標
* @param posY 開始Y座標
* @param interval タイルの間隔
* @return 描画のための Rectangle
*/
public function execute(degree:int, posX:int, posY:int, interval:int):Rectangle {
var shiftX:int = 0;
var shiftY:int = (interval - degree) / 2;
var rect:Rectangle = new Rectangle();
rect.x = posX * interval + shiftX;
rect.y = posY * interval + shiftY;
rect.width = interval;
rect.height = degree;
return rect;
}
}
//}
//package {
import flash.geom.Rectangle;
/**
* 描画ふるまいクラス(垂直)
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class BehaviorVertical implements IBehavior {
/**
* ふるまいの実行(描画のための Rectangle を生成)
* @param degree 段階
* @param posX 開始X座標
* @param posY 開始Y座標
* @param interval タイルの間隔
* @return 描画のための Rectangle
*/
public function execute(degree:int, posX:int, posY:int, interval:int):Rectangle {
var shiftX:int = (interval - degree) / 2;
var shiftY:int = 0;
var rect:Rectangle = new Rectangle();
rect.x = posX * interval + shiftX;
rect.y = posY * interval + shiftY;
rect.width = degree;
rect.height = interval;
return rect;
}
}
//}
//package {
import flash.geom.Rectangle;
/**
* 描画ふるまいクラス(水平と垂直の交互)
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class BehaviorAlter1 implements IBehavior {
/**
* ふるまいの実行(描画のための Rectangle を生成)
* @param degree 段階
* @param posX 開始X座標
* @param posY 開始Y座標
* @param interval タイルの間隔
* @return 描画のための Rectangle
*/
public function execute(degree:int, posX:int, posY:int, interval:int):Rectangle {
var shiftX:int;
var shiftY:int;
var rect:Rectangle = new Rectangle();
if (posX % 2) {
// same BehaviorHorizontal
shiftX = 0;
shiftY = (interval - degree) / 2;
rect.width = interval;
rect.height = degree;
} else {
// same BehaviorVertical
shiftX = (interval - degree) / 2;
shiftY = 0;
rect.width = degree;
rect.height = interval;
}
rect.x = posX * interval + shiftX;
rect.y = posY * interval + shiftY;
return rect;
}
}
//}
//package {
import flash.geom.Rectangle;
/**
* 描画ふるまいクラス(水平と垂直の交互)
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class BehaviorAlter2 implements IBehavior {
/**
* ふるまいの実行(描画のための Rectangle を生成)
* @param degree 段階
* @param posX 開始X座標
* @param posY 開始Y座標
* @param interval タイルの間隔
* @return 描画のための Rectangle
*/
public function execute(degree:int, posX:int, posY:int, interval:int):Rectangle {
var shiftX:int;
var shiftY:int;
var rect:Rectangle = new Rectangle();
if ((posX % 2 + posY % 2) % 2) {
// same BehaviorHorizontal
shiftX = 0;
shiftY = (interval - degree) / 2;
rect.width = interval;
rect.height = degree;
} else {
// same BehaviorVertical
shiftX = (interval - degree) / 2;
shiftY = 0;
rect.width = degree;
rect.height = interval;
}
rect.x = posX * interval + shiftX;
rect.y = posY * interval + shiftY;
return rect;
}
}
//}
//package {
import flash.geom.Rectangle;
/**
* 描画ふるまいクラス(正方形)
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class BehaviorSquare implements IBehavior {
/**
* ふるまいの実行(描画のための Rectangle を生成)
* @param degree 段階
* @param posX 開始X座標
* @param posY 開始Y座標
* @param interval タイルの間隔
* @return 描画のための Rectangle
*/
public function execute(degree:int, posX:int, posY:int, interval:int):Rectangle {
var shiftX:int = (interval - degree) / 2;
var shiftY:int = (interval - degree) / 2;
var rect:Rectangle = new Rectangle();
rect.x = posX * interval + shiftX;
rect.y = posY * interval + shiftY;
rect.width = degree;
rect.height = degree;
return rect;
}
}
//}
//package aquioux.display.bitmapDataEffector {
import flash.display.BitmapData;
/**
* BitmapDataEffector 用 interface
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ interface IEffector {
function applyEffect(value:BitmapData):BitmapData;
}
//}
//package aquioux.display.bitmapDataEffector {
import flash.display.BitmapData;
import flash.filters.ColorMatrixFilter;
import flash.geom.Point;
/**
* ColorMatrixFilter による BitmapData のグレイスケール化(NTSC 系加重平均による)
* 参考:Foundation ActionScript 3.0 Image Effects(P106)
* http://www.amazon.co.jp/gp/product/1430218711?ie=UTF8&tag=laxcomplex-22
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class GrayScale implements IEffector {
private const R:Number = EffectorUtils.LUM_R;
private const G:Number = EffectorUtils.LUM_G;
private const B:Number = EffectorUtils.LUM_B;
private const MATRIX:Array = [
R, G, B, 0, 0,
R, G, B, 0, 0,
R, G, B, 0, 0,
0, 0, 0, 1, 0
];
private const FILTER:ColorMatrixFilter = new ColorMatrixFilter(MATRIX);
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
value.applyFilter(value, value.rect, ZERO_POINT, FILTER);
return value;
}
}
//}
//package aquioux.display.bitmapDataEffector {
import flash.display.BitmapData;
import flash.geom.Point;
/**
* paletteMap による BitmapData の減色
* 「実践画像処理入門」 培風館 内村圭一・上瀧剛 P16 「2.5 濃度値の量子化による減色処理」
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class Posterize implements IEffector {
/*
* 減色の段階
* @param value 段階
*/
public function set degree(value:uint):void {
// value の有効範囲は 2 ~ 256
if (value < 2) value = 2;
if (value > 256) value = 256;
if (_gradation) {
_gradation.fixed = false;
_gradation.length = 0;
} else {
_gradation = new Vector.<uint>();
}
var prevVal:uint = 0xFF;
for (var i:int = 0; i < 256; i++) {
var val:uint = uint(i / (256 / value)) * 255 / (value - 1);
rArray_[i] = val << 16;
gArray_[i] = val << 8;
bArray_[i] = val;
if (prevVal != val) {
_gradation.push(val);
prevVal = val;
}
}
_gradation.fixed = true;
}
// 減色化によって計算された gradation の値を格納する Vector
// degree を set することではじめて有効になる
// length は degree になる
public function get gradation():Vector.<uint> { return _gradation; }
private var _gradation:Vector.<uint>;
// paletteMap の引数となる各 Channel 用の Array
private var rArray_:Array = [];
private var gArray_:Array = [];
private var bArray_:Array = [];
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
/*
* コンストラクタ
*/
public function Posterize() {
degree = 8; // degree のデフォルト
}
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
value.paletteMap(value, value.rect, ZERO_POINT, rArray_, gArray_, bArray_);
return value;
}
}
//}
//package aquioux.display.bitmapDataEffector {
import flash.display.BitmapData;
import flash.filters.BitmapFilterQuality;
import flash.filters.BlurFilter;
import flash.geom.Point;
/**
* BlurFilter による平滑化
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class Smooth implements IEffector {
/*
* ぼかしの強さ
* @param value 数値
*/
public function set strength(value:Number):void {
filter_.blurX = filter_.blurY = value;
}
/*
* ぼかしの質
* @param value 数値
*/
public function set quality(value:int):void {
filter_.quality = value;
}
// ブラーフィルタ
private var filter_:BlurFilter;
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
/*
* コンストラクタ
*/
public function Smooth() {
filter_ = new BlurFilter(2, 2, BitmapFilterQuality.MEDIUM);
}
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
value.applyFilter(value, value.rect, ZERO_POINT, filter_);
return value;
}
}
//}
//package aquioux.display.bitmapDataEffector {
import flash.geom.Point;
/**
* bitmapDataEffector パッケージ内のクラスで共通に使う定数など
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class EffectorUtils {
// BitmapData が備える各種メソッドの destPoint 用
static public const ZERO_POINT:Point = new Point(0, 0);
// グレイスケール用の各チャンネルの重みづけ
// NTSC系加重平均法(YIQ,YCbCr も同じ)
static public const LUM_R:Number = 0.298912;
static public const LUM_G:Number = 0.586611;
static public const LUM_B:Number = 0.114478;
}
//}
//package aquioux.display.colorUtil {
/**
* コサインカーブで色相環的な RGB を計算
* @author Aquioux(YOSHIDA, Akio)
*/
/*public*/ class CycleRGB {
/**
* 32bit カラーのためのアルファ値(0~255)
*/
static public function get alpha():uint { return _alpha; }
static public function set alpha(value:uint):void {
_alpha = (value > 0xFF) ? 0xFF : value;
}
private static var _alpha:uint = 0xFF;
private static const PI:Number = Math.PI; // 円周率
private static const DEGREE120:Number = PI * 2 / 3; // 120度(弧度法形式)
/**
* 角度に応じた RGB を得る
* @param angle HSV のように角度(度数法)を指定
* @return 色(0xNNNNNN)
*/
public static function getColor(angle:Number):uint {
var radian:Number = angle * PI / 180;
var r:uint = (Math.cos(radian) + 1) * 0xFF >> 1;
var g:uint = (Math.cos(radian + DEGREE120) + 1) * 0xFF >> 1;
var b:uint = (Math.cos(radian - DEGREE120) + 1) * 0xFF >> 1;
return r << 16 | g << 8 | b;
}
/**
* 角度に応じた RGB を得る(32bit カラー)
* @param angle HSV のように角度(度数法)を指定
* @return 色(0xNNNNNNNN)
*/
public static function getColor32(angle:Number):uint {
return _alpha << 24 | getColor(angle);
}
}
//}