/*
 * Canvas2Image v0.1
 * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
 * MIT License [http://www.opensource.org/licenses/mit-license.php]
 */

var Canvas2Image = (function() {
	// check if we have canvas support
	var oCanvas = document.createElement("canvas"),
	    sc = String.fromCharCode,
      strDownloadMime = "image/octet-stream",
      bReplaceDownloadMime = false;
	
	// no canvas, bail out.
	if (!oCanvas.getContext) {
		return {
			saveAsBMP : function(){},
			saveAsPNG : function(){},
			saveAsJPEG : function(){}
		}
	}

	var bHasImageData = !!(oCanvas.getContext("2d").getImageData),
	    bHasDataURL = !!(oCanvas.toDataURL),
	    bHasBase64 = !!(window.btoa);

	// ok, we're good
	var readCanvasData = function(oCanvas) {
		var iWidth = parseInt(oCanvas.width),
		    iHeight = parseInt(oCanvas.height);
		return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight);
	}

	// base64 encodes either a string or an array of charcodes
	var encodeData = function(data) {
		var i, aData, strData = "";
		
		if (typeof data == "string") {
			strData = data;
		} else {
			aData = data;
			for (i = 0; i < aData.length; i++) {
				strData += sc(aData[i]);
			}
		}
		return btoa(strData);
	}

	// creates a base64 encoded string containing BMP data takes an imagedata object as argument
	var createBMP = function(oData) {
		var strHeader = '',
		    iWidth = oData.width,
		    iHeight = oData.height;

		strHeader += 'BM';
	
		var iFileSize = iWidth*iHeight*4 + 54; // total header size = 54 bytes
		strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
		strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
		strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
		strHeader += sc(iFileSize % 256);

		strHeader += sc(0, 0, 0, 0, 54, 0, 0, 0); // data offset
		strHeader += sc(40, 0, 0, 0); // info header size

		var iImageWidth = iWidth;
		strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
		strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
		strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
		strHeader += sc(iImageWidth % 256);
	
		var iImageHeight = iHeight;
		strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
		strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
		strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
		strHeader += sc(iImageHeight % 256);
	
		strHeader += sc(1, 0, 32, 0); // num of planes & num of bits per pixel
		strHeader += sc(0, 0, 0, 0); // compression = none
	
		var iDataSize = iWidth*iHeight*4; 
		strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
		strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
		strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
		strHeader += sc(iDataSize % 256); 
	
		strHeader += sc(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); // these bytes are not used
	
		var aImgData = oData.data,
		    strPixelData = "",
		    c, x, y = iHeight,
		    iOffsetX, iOffsetY, strPixelRow;
		
		do {
			iOffsetY = iWidth*(y-1)*4;
			strPixelRow = "";
			for (x = 0; x < iWidth; x++) {
				iOffsetX = 4*x;
				strPixelRow += sc(
					aImgData[iOffsetY + iOffsetX + 2], // B
					aImgData[iOffsetY + iOffsetX + 1], // G
					aImgData[iOffsetY + iOffsetX],     // R
					aImgData[iOffsetY + iOffsetX + 3]  // A
				);
			}
			strPixelData += strPixelRow;
		} while (--y);

		return encodeData(strHeader + strPixelData);
	}

	// sends the generated file to the client
	var saveFile = function(strData) {
		if (!window.open(strData)) {
			document.location.href = strData;
		}
	}

	var makeDataURI = function(strData, strMime) {
		return "data:" + strMime + ";base64," + strData;
	}

	// generates a <img> object containing the imagedata
	var makeImageObject = function(strSource) {
		var oImgElement = document.createElement("img");
		oImgElement.src = strSource;
		return oImgElement;
	}

	var scaleCanvas = function(oCanvas, iWidth, iHeight) {
		if (iWidth && iHeight) {
			var oSaveCanvas = document.createElement("canvas");
			
			oSaveCanvas.width = iWidth;
			oSaveCanvas.height = iHeight;
			oSaveCanvas.style.width = iWidth+"px";
			oSaveCanvas.style.height = iHeight+"px";

			var oSaveCtx = oSaveCanvas.getContext("2d");

			oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iWidth);
			
			return oSaveCanvas;
		}
		return oCanvas;
	}

	return {
		saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) {
			if (!bHasDataURL) return false;
			
			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight),
          strMime = "image/png",
			    strData = oScaledCanvas.toDataURL(strMime);
				
			if (bReturnImg) {
				return makeImageObject(strData);
			} else {
				saveFile(bReplaceDownloadMime ? strData.replace(strMime, strDownloadMime) : strData);
			}
			return true;
		},

		saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) {
			if (!bHasDataURL) return false;

			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight),
			    strMime = "image/jpeg",
			    strData = oScaledCanvas.toDataURL(strMime);
	
			// check if browser actually supports jpeg by looking for the mime type in the data uri. if not, return false
			if (strData.indexOf(strMime) != 5) return false;

			if (bReturnImg) {
				return makeImageObject(strData);
			} else {
        saveFile(bReplaceDownloadMime ? strData.replace(strMime, strDownloadMime) : strData);
			}
			return true;
		},

		saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) {
			if (!(bHasDataURL && bHasImageData && bHasBase64)) return false;

			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight),
          strMime = "image/bmp",
			    oData = readCanvasData(oScaledCanvas),
			    strImgData = createBMP(oData);
				
			if (bReturnImg) {
				return makeImageObject(makeDataURI(strImgData, strMime));
			} else {
        saveFile(makeDataURI(strImgData, strMime));
			}
			return true;
		}
	};
})();
