NESImage
画像を擬似ファミコンカラーパレットで(低精度)減色します。
色数は、ファミコンの同時発色数の制限等は再現してないので最大52色です。
/**
* Copyright o8que ( http://wonderfl.net/user/o8que )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/mx0p
*/
package {
import com.adobe.images.PNGEncoder;
import com.bit101.components.CheckBox;
import com.bit101.components.Label;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.net.FileReference;
[SWF(width="465",height="465")]
public class Main extends Sprite {
private static const MAX_WIDTH:int = 256; // 生成する画像の最大幅
private static const MAX_HEIGHT:int = 224; // 生成する画像の最大高さ
private var _loader:LocalImageLoader;
private var _display:Bitmap;
private var _loadButton:Button;
private var _saveButton:Button;
private var _EDCheck:CheckBox;
public function Main() {
graphics.beginFill(0x000000);
graphics.drawRect(0, 0, 465, 465);
graphics.endFill();
_loader = new LocalImageLoader();
_display = new Bitmap(new BitmapData(1, 1, true, 0x00FFFFFF));
addChild(_display);
_loadButton = new Button(100, 20);
_loadButton.x = 122; _loadButton.y = 420;
_loadButton.setFace(new Label(null, 0, 0, "Load"));
_loadButton.addEventListener(MouseEvent.CLICK, clickLoadButtonHandler);
addChild(_loadButton);
_saveButton = new Button(100, 20);
_saveButton.x = 242; _saveButton.y = 420;
_saveButton.setFace(new Label(null, 0, 0, "Save"));
_saveButton.addEventListener(MouseEvent.CLICK, clickSaveButtonHandler);
addChild(_saveButton);
_saveButton.enabled = false;
_EDCheck = new CheckBox(this, 0, 445, "ErrorDiffusion");
_EDCheck.x = 122 + (int((100 - _EDCheck.width) / 2));
_EDCheck.selected = true;
}
private function clickLoadButtonHandler(event:MouseEvent):void {
_loader.addEventListener(Event.COMPLETE, quantizeImage);
_loader.addEventListener(Event.CANCEL, cancelLoading);
_loader.load();
_loadButton.enabled = false;
}
private function quantizeImage(event:Event):void {
_loader.removeEventListener(Event.COMPLETE, quantizeImage);
_loader.removeEventListener(Event.CANCEL, cancelLoading);
var image:DisplayObject = _loader.content;
image.scaleX = image.scaleY = Math.min(1, MAX_WIDTH / image.width, MAX_HEIGHT / image.height);
var bmd:BitmapData = new BitmapData(image.width, image.height, false, 0x000000);
bmd.draw(image, image.transform.matrix, null, null, null, true);
_display.bitmapData = NESImage.quantize(bmd, _EDCheck.selected);
_display.scaleX = _display.scaleY = Math.min(1, 465 / _display.bitmapData.width, 465 / _display.bitmapData.height);
_display.x = int((465 - _display.width) / 2);
_display.y = int((465 - _display.height) / 2);
_loadButton.enabled = _saveButton.enabled = true;
}
private function cancelLoading(event:Event):void {
_loader.removeEventListener(Event.COMPLETE, quantizeImage);
_loader.removeEventListener(Event.CANCEL, cancelLoading);
_loadButton.enabled = true;
}
private function clickSaveButtonHandler(event:MouseEvent):void {
(new FileReference()).save(PNGEncoder.encode(_display.bitmapData),"NESImage.png");
}
}
}
/* ------------------------------------------------------------------------------------------------
* NESImage
* ------------------------------------------------------------------------------------------------
*/
//package {
import flash.display.BitmapData;
import flash.display.Loader;
import frocessing.color.ColorHSL;
//public
class NESImage {
public static function quantize(source:BitmapData, usesErrorDiffusion:Boolean):BitmapData {
var width:int = source.width;
var height:int = source.height;
var bmd:BitmapData = new BitmapData(width, height, false, 0x000000);
bmd.lock();
var x:int, y:int;
var original:ColorHSL = new ColorHSL();
var quantized:ColorHSL = new ColorHSL();
if (usesErrorDiffusion) {
var error:ColorError;
var errorTable:Vector.<Vector.<ColorError>> = new Vector.<Vector.<ColorError>>(height + 1, true);
for (y = height; y >= 0; y--) {
errorTable[y] = new Vector.<ColorError>(width + 2, true);
for (x = width + 1; x >= 0; x--) {
errorTable[y][x] = new ColorError();
}
}
}
// 誤差拡散を行うかどうかで、各閾値を少し変える
var thresholds:Vector.<Number> = new Vector.<Number>(6, true);
thresholds[0] = (usesErrorDiffusion) ? 0.1 : 0.125;
thresholds[1] = (usesErrorDiffusion) ? 0.9 : 0.875;
thresholds[2] = (usesErrorDiffusion) ? 0.2 : 0.225;
thresholds[3] = (usesErrorDiffusion) ? 0.625 : 0.6;
thresholds[4] = (usesErrorDiffusion) ? 0.875 : 0.85;
thresholds[5] = (usesErrorDiffusion) ? 0.25 : 0.3;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
original.value = source.getPixel(x, y);
// 誤差の補正を加える
if (usesErrorDiffusion) {
error = errorTable[y][x + 1];
original.hsl(original.h + error.h, original.s, original.l + error.l);
}
var originalH:Number = original.h;
var originalS:Number = original.s;
var originalL:Number = original.l;
var quantizedH:Number;
var quantizedS:Number;
var quantizedL:Number;
if (originalL < thresholds[0]) {
// 黒
quantizedH = quantizedS = quantizedL = 0;
}else if (originalL > thresholds[1]) {
// 白
quantizedH = quantizedS = 0;
quantizedL = 1;
}else if (originalS < thresholds[2]) {
// モノクロ
quantizedH = quantizedS = 0;
if (originalL > thresholds[3]) {
if (originalL > thresholds[4]) {
quantizedL = 1;
}else {
quantizedL = 0.75;
}
}else {
if (originalL > thresholds[5]) {
quantizedL = 0.5;
}else {
quantizedL = 0;
}
}
}else {
// カラー
quantizedS = 1;
if (originalH < 135) {
if (originalH < 65) {
if (originalH < 10) {
quantizedH = 0;
}else if (originalH < 30) {
quantizedH = 20;
}else {
quantizedH = 40;
}
}else {
if (originalH < 105) {
quantizedH = 90;
}else {
quantizedH = 120;
}
}
}else if (originalH < 255) {
if (originalH < 195) {
if (originalH < 165) {
quantizedH = 150;
}else {
quantizedH = 180;
}
}else {
if (originalH < 225) {
quantizedH = 210;
}else {
quantizedH = 240;
}
}
}else {
if (originalH < 320) {
if (originalH < 285) {
quantizedH = 270;
}else {
quantizedH = 300;
}
}else {
if (originalH < 350) {
quantizedH = 340;
}else {
quantizedH = 0;
}
}
}
if (originalL > 0.5) {
if (originalL > 0.7) {
quantizedL = 0.8;
}else {
quantizedL = 0.6;
}
}else {
if (originalL > 0.3) {
quantizedL = 0.4;
}else {
quantizedL = 0.2;
}
}
}
quantized.hsl(quantizedH, quantizedS, quantizedL);
bmd.setPixel(x, y, quantized.value);
// 元の色と変換後の色の誤差を拡散させる
if (usesErrorDiffusion) {
var diffH1:Number = (quantizedS == 1) ? originalH - quantizedH : 0;
var diffH2:Number = (diffH1 < 0) ? 360 + diffH1 : 360 - diffH1;
var errorH:Number = ((Math.abs(diffH1) < Math.abs(diffH2)) ? diffH1 : diffH2) * 0.0625;
var errorL:Number = (originalL - quantizedL) * 0.0625;
errorTable[y][x + 2].add(errorH * 7, errorL * 7);
errorTable[y + 1][x].add(errorH * 3, errorL * 3);
errorTable[y + 1][x + 1].add(errorH * 5, errorL * 5);
errorTable[y + 1][x + 2].add(errorH, errorL);
}
}
}
bmd.unlock();
return bmd;
}
}
//}
class ColorError {
public var h:Number;
public var l:Number;
public function ColorError() {
this.h = this.l = 0;
}
public function add(h:Number, l:Number):void {
this.h += h;
this.l += l;
}
}
/* ------------------------------------------------------------------------------------------------
* Button
* ------------------------------------------------------------------------------------------------
*/
//package {
import flash.display.DisplayObject;
import flash.display.GradientType;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.filters.DropShadowFilter;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Point;
//public
class Button extends Sprite {
private var _size:Point;
private var _border:Shape;
private var _background:Shape;
private var _faceContainer:Sprite;
private var _face:DisplayObject;
private var _facePosition:Point;
private var _hovered:Boolean;
private var _pressed:Boolean;
private var _selected:Boolean;
private var _enabled:Boolean;
public function Button(width:int, height:int) {
_size = new Point(width, height);
addChild(_border = new Shape());
addChild(_background = new Shape());
addChild(_faceContainer = new Sprite());
_faceContainer.addChild(_face = new Shape());
_facePosition = new Point(0, 0);
_hovered = _pressed = false;
var matrix:Matrix = new Matrix();
matrix.createGradientBox(_size.x - 2, _size.y - 2, Math.PI / 2, 1, 1);
_background.graphics.beginGradientFill(GradientType.LINEAR, [0xFFFFFF, 0xE0E0E0], [1, 1], [0, 255], matrix);
_background.graphics.drawRect(1, 1, _size.x - 2, _size.y - 2);
_background.graphics.endFill();
selected = false;
enabled = true;
mouseChildren = false;
addEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
}
private function rollOverHandler(event:MouseEvent):void {
_hovered = true;
draw();
addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
}
private function rollOutHandler(event:MouseEvent):void {
removeEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
_hovered = false;
draw();
}
private function mouseDownHandler(event:MouseEvent):void {
_pressed = true;
draw();
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
}
private function mouseUpHandler(event:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
_pressed = false;
draw();
}
private static const DOWN:Array = [new DropShadowFilter(1, 45, 0x404040, 0.5, 1, 1, 1, 1, true)];
private static const UP:Array = [];
private static const HOVER:ColorTransform = new ColorTransform(0.5, 0.5, 0.5, 1, 128, 128, 128);
private static const NORMAL:ColorTransform = new ColorTransform(1, 1, 1);
private static const DISABLED:ColorTransform = new ColorTransform(0.7, 0.7, 0.7);
private function draw():void {
_border.graphics.clear();
_border.graphics.beginFill((_selected) ? 0xFFFF00 : 0xC0C0C0);
_border.graphics.drawRect(0, 0, _size.x, _size.y);
_border.graphics.endFill();
_background.filters = (_pressed) ? DOWN : UP;
_face.x = _facePosition.x + int(_pressed);
_face.y = _facePosition.y + int(_pressed);
transform.colorTransform = (_enabled) ? ((_hovered) ? HOVER : NORMAL) : DISABLED;
}
public function setFace(face:DisplayObject):void {
_faceContainer.removeChild(_face);
_face = face;
_face.x = _facePosition.x = (_size.x - _face.width) / 2;
_face.y = _facePosition.y = (_size.y - _face.height) / 2;
_faceContainer.addChild(_face);
}
public function get selected():Boolean { return _selected; }
public function get enabled():Boolean { return _enabled; }
public function set selected(value:Boolean):void {
_selected = value;
draw();
}
public function set enabled(value:Boolean):void {
_enabled = buttonMode = mouseEnabled = value;
draw();
}
}
//}
/* ------------------------------------------------------------------------------------------------
* LocalImageLoader
* ------------------------------------------------------------------------------------------------
*/
//package {
import flash.display.DisplayObject;
import flash.display.Loader;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.net.FileFilter;
import flash.net.FileReference;
//public
class LocalImageLoader extends EventDispatcher {
private var _loader:Loader;
private var _file:FileReference;
private var _fileFilter:Array;
public function LocalImageLoader() {
_loader = new Loader();
_file = new FileReference();
_fileFilter = [new FileFilter("Image (*.png;*.jpg;*.gif)", "*.png;*.jpg;*.gif")];
}
public function load():void {
_file.addEventListener(Event.SELECT, onFileSelected);
_file.addEventListener(Event.CANCEL, cancelFileSelection);
_file.browse(_fileFilter);
}
private function onFileSelected(event:Event):void {
_file.addEventListener(Event.COMPLETE, onFileLoaded);
_file.load();
}
private function onFileLoaded(event:Event):void {
removeFileListeners();
_loader.unload();
_loader.contentLoaderInfo.addEventListener(Event.INIT, onLoadCompleted);
_loader.loadBytes(_file.data);
}
private function onLoadCompleted(event:Event):void {
_loader.contentLoaderInfo.removeEventListener(Event.INIT, onLoadCompleted);
dispatchEvent(new Event(Event.COMPLETE));
}
private function cancelFileSelection(event:Event):void {
removeFileListeners();
dispatchEvent(new Event(Event.CANCEL));
}
private function removeFileListeners():void {
_file.removeEventListener(Event.SELECT, onFileSelected);
_file.removeEventListener(Event.CANCEL, cancelFileSelection);
_file.removeEventListener(Event.COMPLETE, onFileLoaded);
}
public function get content():DisplayObject { return _loader.content; }
}
//}