package
{
	import flash.display.*;
	import com.adobe.images.*;
	import flash.utils.*;
	import flash.net.*;
	import flash.geom.Matrix;
	import flash.geom.Rectangle;
	import flash.geom.Point;
	
	public class PNGExportHelper
	{
		public static var UseExpandedBitmaps = false;
		public static var AlphaThreshold = 5;
		
		private static function ExpandBitmapBorders(bd:BitmapData, bgColor = 0x0, passes = 1):BitmapData
		{
			/*
			var thresh:BitmapData = new BitmapData(bd.width, bd.height, true, bgColor);
			thresh.threshold(bd, bd.rect, new Point(), "<", (20 << 24), bgColor, 0xFF000000, true);
			*/
			var sampled:BitmapData = new BitmapData(bd.width, bd.height, true, bgColor);
			bd.lock();
			sampled.lock();
			
			for (var y = 0; y < bd.height; y++)
			{
				for (var x = 0; x < bd.width; x++)
				{
					var oldVal:uint = bd.getPixel32(x, y);
					var a:uint = (oldVal & 0xFF000000) >>> 24;
					var rgb:uint = (oldVal & 0xFFFFFF);
					if (a > AlphaThreshold)
					{
						sampled.setPixel32(x, y, (0xFF << 24) | rgb);
					}
					else
					{
						sampled.setPixel32(x, y, bgColor);
					}
				}
			}
			sampled.unlock();
			bd.unlock();
			
			return ExpandBitmapBordersInternal(sampled, bgColor, passes);
		}
		
		private static function ExpandBitmapBordersInternal(bd:BitmapData, bgColor, passes):BitmapData
		{	
			var output:BitmapData = new BitmapData(bd.width, bd.height, true, bgColor);
			
			var scale = 16;
			var scaled:BitmapData = new BitmapData(bd.width/scale, bd.height/scale, true, bgColor); 
			var mat:Matrix = new Matrix();
			mat.scale(1/scale, 1/scale);
			scaled.draw(bd, mat, null, null, null, true);
			
			output.lock();
			
			var colors:Array = new Array();
			var weights:Array = new Array();

			for (var oy = 0; oy < scaled.height; oy++)
			{
				
				for (var ox = 0; ox < scaled.width; ox++)
				{
					
					var scaledVal:uint = scaled.getPixel32(ox, oy);
					var scaleda:uint = (scaledVal & 0xFF000000) >>> 24;
					if (scaleda == 0 || scaleda == 255)
					{
						output.copyPixels(bd, new Rectangle(ox * scale, oy * scale, scale, scale), new Point(ox * scale, oy * scale));
						continue;
					} else {
						//_strace(scaleda+"");
					}
					
					
					for (var y = oy * scale; y < oy * scale + scale; y++)
					{
						for (var x = ox * scale; x < ox * scale + scale; x++)
						{
							var oldVal:uint = bd.getPixel32(x, y);
							var a:uint = (oldVal & 0xFF000000) >>> 24;
							var rgb:uint = (oldVal & 0xFFFFFF);
							
							if (a == 0 && rgb == bgColor)
							{
								colors.length = 0;
								weights.length = 0;
								
								for (var y1 = y - 2; y1 <= y + 2; y1++)
								{
									for (var x1 = x - 2; x1 <= x + 2; x1++)
									{
										var dist = Math.abs(x1 - x) + Math.abs(y1 - y);
										if (dist >= 3) continue;
										
										if (x1 >= 0 && x1 < bd.width && y1 >= 0 && y1 < bd.height)
										{
											colors.push(bd.getPixel(x1, y1));
											weights.push(1 - dist * 0.2);
										}
									}
								}
								
								var newVal = AveragePixels(colors, weights, bgColor);
								if (newVal == null)
								{
									newVal = oldVal;
								}
								else
								{
									var newR:uint = (newVal & 0xFF0000) >> 16;
									var newG:uint = (newVal & 0xFF00) >> 8;
									var newB:uint = (newVal & 0xFF);
									
									if (newVal != 0 && colors.length >= 4)
									{
										var bp = true;
									}
									
									var sorted = colors.sort(function(a, b)
									{
										var aR:uint = (a & 0xFF0000) >> 16;
										var aG:uint = (a & 0xFF00) >> 8;
										var aB:uint = (a & 0xFF);
										
										var bR:uint = (b & 0xFF0000) >> 16;
										var bG:uint = (b & 0xFF00) >> 8;
										var bB:uint = (b & 0xFF);
										
										var diffA = Math.abs(aR - newR) + Math.abs(aG - newG) + Math.abs(aB - newB);
										var diffB = Math.abs(bR - newR) + Math.abs(bG - newG) + Math.abs(bB - newB);
										
										return diffA - diffB;
										
									}, Array.RETURNINDEXEDARRAY | Array.DESCENDING);
									
									var toRemove = Math.min(2, colors.length - 4);
									var removed = 0;
									for (var i = 0; i < colors.length; i++)
									{
										var index = sorted.indexOf(i + removed);
										if (index < toRemove)
										{
											colors.splice(i, 1);
											weights.splice(i, 1);
											removed++;
											i--;
											if (removed == toRemove) break;
										}
									}
									
									var fixOutliersVal = AveragePixels(colors, weights, bgColor);
									if (fixOutliersVal == null) newVal = (0xFF << 24) | newVal;
									
									newVal = (0xFF << 24) | fixOutliersVal;
								}
								
								output.setPixel32(x, y, newVal);
							}
							else
							{
								output.setPixel32(x, y, oldVal);
							}
						}
					}
				}//end outer scaled x
			}//End outer scaled y
			
			bd.unlock();
			output.unlock();
			
			passes--;
			if (passes > 0)
			{				
				return ExpandBitmapBordersInternal(output, bgColor, passes);
			}
			else
			{
				return output;
			}
		}
		
		private static function AveragePixels(colors:Array, weights:Array, bgColor:uint):uint
		{
			var rTotal = 0;
			var gTotal = 0;
			var bTotal = 0;
			
			var wTotal = 0;
			
			for (var i = 0; i < colors.length; i++)
			{
				var color:uint = colors[i];
				var a:uint = (color & 0xFF000000) >>> 24;
				var rgb:uint = (color & 0xFFFFFF);
				
				if (a == 0 && rgb == bgColor)
				{
					colors.splice(i, 1);
					weights.splice(i, 1);
					i--;
					continue;
				}
				
				var r:uint = (rgb & 0xFF0000) >>> 16;
				var g:uint = (rgb & 0xFF00) >>> 8;
				var b:uint = (rgb & 0xFF);
				var w = weights[i];
				
				rTotal += w * r;
				gTotal += w * g;
				bTotal += w * b;
				
				wTotal += w;
			}
			
			if (wTotal == 0)
			{
				return null;
			}
			
			rTotal /= wTotal;
			gTotal /= wTotal;
			bTotal /= wTotal;
			
			var rFinal = Math.max(0, Math.min(255, Math.round(rTotal)));
			var gFinal = Math.max(0, Math.min(255, Math.round(gTotal)));
			var bFinal = Math.max(0, Math.min(255, Math.round(bTotal)));
			
			return (uint(rFinal) << 16) | (uint(gFinal) << 8) | (uint(bFinal));
		}
		
		
		
		public static function GetExportPNG(bd:BitmapData):ByteArray
		{
			if (!UseExpandedBitmaps) return PNGEncoder.encode(bd);
						
			var expanded:BitmapData = ExpandBitmapBorders(bd, 0x0, 2);
			
			//var fr:FileReference = new FileReference();
			//fr.save(PNGEncoder.encode(expanded), "output.png");
			
			return PNGEncoderWithAlpha.encode(expanded, bd);
		}
		
	}
}