Error Diffusion
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/6Q28
*/
// forked from Aquioux's Dither
package {
import flash.geom.Matrix;
//import aquioux.display.bitmapDataEffector.GrayScale;
//import aquioux.utils.ButtonMediator;
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.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.system.LoaderContext;
/**
* ハーフトーニング(誤差拡散法)
* @see http://aquioux.net/blog/?p=2708
* @author YOSHIDA, Akio (Aquioux)
*/
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#FFFFFF")]
public class Main extends Sprite {
// ビューア BitmapData
private var viewBmd_:BitmapData;
// BitmapData#getVector で得られる値
private var sourcePixelList_:Vector.<uint>; // ソース画像
private var grayPixelList_:Vector.<uint>; // グレイスケール画像
// BitmapData#setVector 用
private var rect_:Rectangle;
// ディザクラス
private var errorDiffusion_:ErrorDiffusion; // 誤差拡散法
// ボタン管理
private var buttonMediator_:ButtonMediator;
// コンストラクタ
public function Main() {
var url:String = "http://assets.wonderfl.net/images/related_images/3/39/39f6/39f64cad1c50914c2c459386feeddf6d915cea06m";
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 loadedBmd:BitmapData = event.target.loader.content.bitmapData;
var sourceBmd:BitmapData = new BitmapData(180, 215);
sourceBmd.draw(loadedBmd, new Matrix(180/200, 0, 0, 215/200));
rect_ = sourceBmd.rect;
sourcePixelList_ = sourceBmd.getVector(rect_);
// グレイスケール BitmapData
var grayBmd:BitmapData = sourceBmd.clone();
new GrayScale().applyEffect(grayBmd);
grayPixelList_ = grayBmd.getVector(rect_);
grayBmd.dispose();
// ビューア
viewBmd_ = sourceBmd;
var viewBm:Bitmap = new Bitmap(viewBmd_);
viewBm.x = (stage.stageWidth - viewBm.width ) / 2 >> 0;
viewBm.y = (stage.stageHeight - viewBm.height ) / 2 >> 0;
addChild(viewBm);
// ディザクラス
errorDiffusion_ = new ErrorDiffusion();
// ボタン
var buttonWidth:int = 100;
var buttonHeight:int = 20;
var button0:PushButton = new PushButton(this, 0, buttonHeight * 0, "Original", originalHander);
var button1:PushButton = new PushButton(this, 0, buttonHeight * 1, "Floyd-Steinberg", errorDiffusion1);
var button2:PushButton = new PushButton(this, 10, buttonHeight * 2, "false Floyd-Steinberg", errorDiffusion2);
var button3:PushButton = new PushButton(this, 10, buttonHeight * 3, "Fan", errorDiffusion3);
var button4:PushButton = new PushButton(this, 10, buttonHeight * 4, "Shiau-Fan", errorDiffusion4);
var button5:PushButton = new PushButton(this, 10, buttonHeight * 5, "Shiau-Fan 2", errorDiffusion5);
var button6:PushButton = new PushButton(this, 0, buttonHeight * 6, "Jarvis, Judice & Ninke", errorDiffusion6);
var button7:PushButton = new PushButton(this, 0, buttonHeight * 7, "Stucki", errorDiffusion7);
var button8:PushButton = new PushButton(this, 0, buttonHeight * 8, "Burkes", errorDiffusion8);
var button9:PushButton = new PushButton(this, 0, buttonHeight * 9, "Sierra2", errorDiffusion9);
var button10:PushButton = new PushButton(this, 0, buttonHeight * 10, "Sierra-2-4A", errorDiffusion10);
var button11:PushButton = new PushButton(this, 0, buttonHeight * 11, "Sierra3", errorDiffusion11);
var button12:PushButton = new PushButton(this, 0, buttonHeight * 12, "Atkinson", errorDiffusion12);
button0.width = buttonWidth;
button1.width = buttonWidth;
button2.width = buttonWidth;
button3.width = buttonWidth;
button4.width = buttonWidth;
button5.width = buttonWidth;
button6.width = buttonWidth;
button7.width = buttonWidth;
button8.width = buttonWidth;
button9.width = buttonWidth;
button10.width = buttonWidth;
button11.width = buttonWidth;
button12.width = buttonWidth;
button0.height = buttonHeight;
button1.height = buttonHeight;
button2.height = buttonHeight;
button3.height = buttonHeight;
button4.height = buttonHeight;
button5.height = buttonHeight;
button6.height = buttonHeight;
button7.height = buttonHeight;
button8.height = buttonHeight;
button9.height = buttonHeight;
button10.height = buttonHeight;
button11.height = buttonHeight;
button12.height = buttonHeight;
// ボタン管理
buttonMediator_ = new ButtonMediator();
buttonMediator_.prevButton = button0;
}
// オリジナル画像
private function originalHander(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
viewBmd_.setVector(rect_, sourcePixelList_);
}
// 誤差拡散法(Floyd-Steinberg)
// http://sekal.ics.p.lodz.pl/~andrey/html/po/doc/org/plant/kzpif/filters/dithering/FloydSteinberg.html
// http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
// http://caca.zoy.org/wiki/libcaca/study/3
// http://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
// http://www.graphicsacademy.com/what_ditherfs.php
// http://braque.c.u-tokyo.ac.jp/sakuma/HTML_FILES_IN_RIKKYO/99JYOSHO/DITHER/dither/node8.html
private function errorDiffusion1(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 3;
errorDiffusion_.matrixY = 2;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 7,
3, 5, 1
]);
errorDiffusion_.divisor = 16;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(false Floyd-Steinberg)
// http://sekal.ics.p.lodz.pl/~andrey/html/po/doc/org/plant/kzpif/filters/dithering/FalseFloydSteinberg.html
// http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
private function errorDiffusion2(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 3;
errorDiffusion_.matrixY = 2;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 3,
0, 3, 2
]);
errorDiffusion_.divisor = 8;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(Fan)
// http://caca.zoy.org/wiki/libcaca/study/3
private function errorDiffusion3(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 5;
errorDiffusion_.matrixY = 2;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 0, 7, 0,
1, 3, 5, 0, 0
]);
errorDiffusion_.divisor = 16;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(Shiau-Fan)
// http://caca.zoy.org/wiki/libcaca/study/3
private function errorDiffusion4(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 5;
errorDiffusion_.matrixY = 2;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 0, 4, 0,
1, 1, 2, 0, 0
]);
errorDiffusion_.divisor = 8;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(Shiau-Fan 2)
// http://caca.zoy.org/wiki/libcaca/study/3
private function errorDiffusion5(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 7;
errorDiffusion_.matrixY = 2;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 0, 0, 8, 0, 0,
1, 1, 2, 4, 0, 0, 0
]);
errorDiffusion_.divisor = 16;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(Jarvis, Judice & Ninke)
// http://sekal.ics.p.lodz.pl/~andrey/html/po/doc/org/plant/kzpif/filters/dithering/JarvisJudiceNinke.html
// http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
// http://caca.zoy.org/wiki/libcaca/study/3 -> 微妙に異なる
// http://www.graphicsacademy.com/what_ditherjarvis.php
// http://braque.c.u-tokyo.ac.jp/sakuma/HTML_FILES_IN_RIKKYO/99JYOSHO/DITHER/dither/node9.html
private function errorDiffusion6(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 5;
errorDiffusion_.matrixY = 3;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 0, 7, 5,
3, 5, 7, 5, 3,
1, 3, 5, 3, 1
]);
errorDiffusion_.divisor = 48;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(Stucki)
// http://sekal.ics.p.lodz.pl/~andrey/html/po/doc/org/plant/kzpif/filters/dithering/Stucki.html
// http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
// http://caca.zoy.org/wiki/libcaca/study/3
// http://www.graphicsacademy.com/what_ditherstucki.php
private function errorDiffusion7(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 5;
errorDiffusion_.matrixY = 3;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 0, 8, 4,
2, 4, 8, 4, 2,
1, 2, 4, 2, 1
]);
errorDiffusion_.divisor = 42;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(Burkes)
// http://sekal.ics.p.lodz.pl/~andrey/html/po/doc/org/plant/kzpif/filters/dithering/Burkes.html
// http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
// http://caca.zoy.org/wiki/libcaca/study/3
private function errorDiffusion8(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 5;
errorDiffusion_.matrixY = 2;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 0, 8, 4,
2, 4, 8, 4, 2
]);
errorDiffusion_.divisor = 32;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(Sierra2)
// http://sekal.ics.p.lodz.pl/~andrey/html/po/doc/org/plant/kzpif/filters/dithering/Sierra2.html
// http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
// http://caca.zoy.org/wiki/libcaca/study/3
private function errorDiffusion9(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 5;
errorDiffusion_.matrixY = 2;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 0, 4, 3,
1, 2, 3, 2, 1
]);
errorDiffusion_.divisor = 16;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(Sierra-2-4A)
// http://sekal.ics.p.lodz.pl/~andrey/html/po/doc/org/plant/kzpif/filters/dithering/Sierra24A.html
// http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
// http://caca.zoy.org/wiki/libcaca/study/3
private function errorDiffusion10(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 3;
errorDiffusion_.matrixY = 2;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 2,
1, 1, 0
]);
errorDiffusion_.divisor = 4;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(Sierra3)
// http://sekal.ics.p.lodz.pl/~andrey/html/po/doc/org/plant/kzpif/filters/dithering/Sierra3.html
// http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
// http://caca.zoy.org/wiki/libcaca/study/3
private function errorDiffusion11(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 5;
errorDiffusion_.matrixY = 3;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 0, 5, 3,
2, 4, 5, 4, 2,
0, 2, 3, 2, 0
]);
errorDiffusion_.divisor = 32;
errorDiffusion_.execute(viewBmd_);
}
// 誤差拡散法(Atkinson)
// http://caca.zoy.org/wiki/libcaca/study/3
private function errorDiffusion12(event:Event):void {
// ボタンの処理
buttonMediator_.buttonChange(PushButton(event.target));
// ディザ実行
viewBmd_.setVector(rect_, grayPixelList_);
errorDiffusion_.matrixX = 5;
errorDiffusion_.matrixY = 3;
errorDiffusion_.matrix = Vector.<Number>([
0, 0, 0, 1, 1,
0, 1, 1, 1, 0,
0, 0, 1, 0, 0
]);
errorDiffusion_.divisor = 8;
errorDiffusion_.execute(viewBmd_);
}
}
}
//package {
import flash.display.BitmapData;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.geom.Rectangle;
/**
* 誤差拡散法
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class ErrorDiffusion extends EventDispatcher {
/**
* 誤差拡散マトリックスのX軸方向の要素数
*/
public function get matrixX():int { return _matrixX; }
public function set matrixX(value:int):void { _matrixX = value; }
private var _matrixX:int;
/**
* 誤差拡散マトリックスのY軸方向の要素数
*/
public function get matrixY():int { return _matrixY; }
public function set matrixY(value:int):void { _matrixY = value; }
private var _matrixY:int;
/**
* 誤差拡散マトリックス
*/
public function get matrix():Vector.<Number> { return _matrix; }
public function set matrix(value:Vector.<Number>):void { _matrix = value; }
private var _matrix:Vector.<Number>;
/**
* マトリックス変換中に使用する除数
* setter で設定する場合は、対象の matrix を設定した後で、実行すること
*/
public function get divisor():Number { return _divisor; }
public function set divisor(value:Number):void {
_divisor = value;
if (_divisor != 1) divisionMatrix();
}
private var _divisor:Number;
/**
* コンストラクタ
*/
public function ErrorDiffusion(matrixX:int = 0, matrixY:int = 0, matrix:Vector.<Number> = null, divisor:Number = 1) {
_matrixX = matrixX;
_matrixY = matrixY;
_matrix = matrix;
_divisor = divisor;
if (_divisor != 1) divisionMatrix();
}
// _divisor が 1 でなかった場合、マトリックス変換を実行
private function divisionMatrix():void {
var len:int = _matrix.length;
for (var i:int = 0; i < len; i++) _matrix[i] /= _divisor;
}
/**
* ディザ実行
*/
public function execute(bmd:BitmapData):void {
var imageWidth:int = bmd.width;
var imageHeight:int = bmd.height;
var rect:Rectangle = bmd.rect;
// 振り分け誤差値格納リスト
var errorList:Vector.<Number> = new Vector.<Number>(imageWidth * imageHeight, true);
var pixelList:Vector.<uint> = bmd.getVector(rect);
var pixelLen:int = pixelList.length;
var matrixLen:int = _matrix.length;
var matrixShift:int = -_matrixX / 2 >> 0;
// 走査
for (var i:int = 0; i < pixelLen; i++) {
var f:int = pixelList[i] & 0xFF; // 当該ピクセルの階調値
f += errorList[i]; // 以前のピクセルから振り分けられた誤差を加える
var g:int;
if (f > 127) {
pixelList[i] = 0xFFFFFFFF;
g = 255;
} else {
pixelList[i] = 0xFF000000;
g = 0;
}
var e:Number = f - g; // 今後のピクセルに振り分ける誤差の値
// 誤差の振り分け
var pixelIdxX:int = i % imageWidth; // BitmapData の現在処理しているピクセルのX座標値
var pixelIdxY:int = i / imageWidth >> 0; // BitmapData の現在処理しているピクセルのY座標値
// 誤差拡散マトリックスの走査
for (var j:int = 0; j < matrixLen; j++) {
var matrixIdxX:int = j % _matrixX; // 誤差拡散マトリックスの誤差の振り分け先のX座標値
var matrixIdxY:int = j / _matrixX >> 0; // 誤差拡散マトリックスの誤差の振り分け先のY座標値
var targetIdxX:int = pixelIdxX + matrixIdxX + matrixShift; // 誤差の振り分け先の BitmapData ピクセルのX座標値
var targetIdxY:int = pixelIdxY + matrixIdxY; // 誤差の振り分け先の BitmapData ピクセルのY座標値
if (targetIdxX >= 0 && targetIdxX < imageWidth && targetIdxY >= 0 && targetIdxY < imageHeight)
// 振り分け誤差値を当該ピクセルに対応する格納リストの場所に格納
errorList[targetIdxY * imageWidth + targetIdxX] += e * _matrix[matrixIdxY * _matrixX + matrixIdxX];
}
}
bmd.setVector(rect, pixelList);
dispatchEvent(new Event(Event.COMPLETE));
}
}
//}
//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;
/**
* BitmapDataEffector 用 interface
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ interface IEffector {
function applyEffect(value:BitmapData):BitmapData;
}
//}
//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;
//static public const LUM_R:Number = 1/3;
//static public const LUM_G:Number = 1/3;
//static public const LUM_B:Number = 1/3;
// HDTV
//static public const LUM_R:Number = 0.2126;
//static public const LUM_G:Number = 0.7152;
//static public const LUM_B:Number = 0.0722;
}
//}
//package aquioux.utils {
/**
* 複数あるボタンの調停
* 一度押したボタンは、別のボタンが押されるまで無効になる
* @author Aquioux(Yoshida, Akio)
*/
import com.bit101.components.PushButton;
/*public*/ class ButtonMediator {
// 前回押したボタン
public function set prevButton(value:PushButton):void {
_prevButton = value;
_prevButton.enabled = false; // 無効にする
}
private var _prevButton:PushButton;
/**
* コンストラクタ
*/
public function ButtonMediator() {
}
/**
* ボタン切り替え
* @param button 今回押したボタン
*/
public function buttonChange(button:PushButton):void {
// 前回押したボタンを有効に戻す
if (_prevButton != null) _prevButton.enabled = true;
// 今回押したボタンを前回押したボタンにして、無効にする
prevButton = button;
}
}
//}