BitmapDataを使ったモザイクエフェクト

最近仕事で忙殺されていて、趣味のプログラムを全然書いてなかったので久しぶりに書いてみました。

作ってみたのは、スライドショーに使えそうなモザイクエフェクトです。

モザイクは、画像の拡大縮小によって実現しています。

まず、画像を縮小コピーします。縮小コピーするとその縮小率に応じて元画像より画素が減ります。これを元の大きさに拡大すると減った分の画素が縮小した時に残った画素で埋められるためモザイク状の画像になります。

ソースコード

以下、コードです。Main.asがスライドショー本体で、MozaicEffect.asがモザイクエフェクトのコードです。

//Main.as
package {
	import flash.display.*;
	import flash.geom.*;
	import flash.events.*;
	import flash.utils.*;
	import flash.ui.*;

	[SWF(width="600", height="600", backgroundColor="#000000", frameRate="30")]
	public class Main extends Sprite {
		[Embed(source="sample1.png")]
		public var Sample1:Class;

		[Embed(source="sample2.png")]
		public var Sample2:Class;

		private var currentBitmap:Bitmap;
		private var imageList:Array;
		private var currentIndex:int;
		private var showEffect:MozaicEffect;
		private var hideEffect:MozaicEffect;
		public function Main() {
			stage.addEventListener(KeyboardEvent.KEY_DOWN, handlerKeydown);
			stage.addEventListener(MouseEvent.CLICK,  handlerClick);

			initImageList();
			currentBitmap = new Bitmap();
			addChild(currentBitmap);

			// init effects
			hideEffect = new MozaicEffect();
			hideEffect.useTimer = false;
			showEffect = new MozaicEffect();
			showEffect.reverse = true;
			showEffect.useTimer = false;

			// show the first image
			showNextImage();
		}

		private function initImageList():void {
			imageList = [new Sample1(), new Sample2()];
			currentIndex = -1;
		}

		private function showNextImage():void {
			var prev:BitmapData = currentImageData;
			currentIndex = (currentIndex + 1) % imageList.length;
			var next:BitmapData = currentImageData;

			var self:Main = this;
			showEffect.source = next;
			showEffect.addEventListener(Event.COMPLETE, function(e:Event):void {
				self.currentBitmap.bitmapData = next;
				if ( self.contains(hideEffect) ) {
					self.removeChild(hideEffect);
				}
				if ( self.contains(showEffect) ) {
					self.removeChild(showEffect);
				}
			});

			if ( prev != null ) {
				hideEffect.source = prev;
				addChild(hideEffect);
				hideEffect.addEventListener(Event.COMPLETE, function(e:Event):void {
					self.addChild(showEffect);
					showEffect.play();
				});
				hideEffect.play();
			}
			else {
				addChild(showEffect);
				showEffect.play();
			}
		}

		private function get currentImageData():BitmapData {
			if ( 0 <= currentIndex && currentIndex < imageList.length ) {
				return imageList[currentIndex].bitmapData;
			}
			return null;
		}

		private function handlerKeydown(e:KeyboardEvent):void {
			if ( e.keyCode == Keyboard.SPACE ) {
				showNextImage();
			}
		}

		private function handlerClick(e:MouseEvent):void {
			showNextImage();
		}
	}
}
// MozaicEffect.as
package {
	import flash.events.*;
	import flash.display.*;
	import flash.geom.*;
	import flash.utils.*;

	public class MozaicEffect extends Sprite {
		public var endCount:int;
		public var reverse:Boolean;
		public var interval:uint;
		public var useTimer:Boolean;

		private var counter:int;
		private var _bgColor:uint;
		private var _source:BitmapData;
		private var buffer:Bitmap;
		private var timer:Timer;
		public function MozaicEffect() {
			endCount = 12;
			counter = 0;
			reverse = false;
			bgColor = 0x000000;

			useTimer = true;
			interval = 100;
		}

		public function play():void {
			if ( source != null ) {
				counter = 0;
				var bmpData:BitmapData = new BitmapData(source.width, source.height, false, 0x000000);
				if ( !reverse ) {
					bmpData.copyPixels(source, new Rectangle(0, 0, source.width, source.height), new Point(0, 0));
				}
				buffer = new Bitmap(bmpData);
				addChild(buffer);
				if ( useTimer ) {
					timer = new Timer(interval, 0);
					timer.addEventListener(TimerEvent.TIMER, updateDisplay);
					timer.start();
				}
				else {
					addEventListener(Event.ENTER_FRAME, updateDisplay);
				}
			}
		}

		public function stop():void {
			if ( hasEventListener(Event.ENTER_FRAME) ) {
				removeEventListener(Event.ENTER_FRAME, updateDisplay);
			}
			if ( useTimer ) {
				timer.removeEventListener(TimerEvent.TIMER, updateDisplay);
				timer.stop();
			}
		}

		public function get bgColor():uint {
			return _bgColor;
		}

		public function set bgColor(color:uint):void {
			color = _bgColor;
		}

		public function get source():BitmapData {
			return _source;
		}

		public function set source(data:BitmapData):void {
			_source = data;
		}

		private function mozaic(src:BitmapData, scale:uint):void {
			var mtrx:Matrix = new Matrix();
			mtrx.scale(1.0 / (scale * scale), 1.0 / (scale * scale));
			var half:BitmapData = new BitmapData(src.width / scale, src.height / scale, false, 0x000000);
			half.draw(src, mtrx);
			var double:uint = scale * scale;
			mtrx.scale(double * double, double * double);
			src.draw(half, mtrx);
			half.dispose();
		}

		private function updateDisplay(e:Event):void {
			var bmpData:BitmapData = buffer.bitmapData;
			if ( counter > endCount ) {
				stop();
				if ( !reverse ) {
					bmpData.fillRect(new Rectangle(0, 0, source.width, source.height), bgColor);
				}
				var event:Event = new Event(Event.COMPLETE);
				dispatchEvent(event);
			}
			else {
				bmpData.copyPixels(source, new Rectangle(0, 0, source.width, source.height), new Point(0, 0));
				if ( reverse ) {
					mozaic(bmpData, endCount - counter + 1);
				}
				else {
					mozaic(bmpData, counter + 1);
				}
			}
			counter++;
		}
	}
}