/*Copyright (C) 2011 by Jim SaundersPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included inall copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS INTHE SOFTWARE.*/function jqScribbleBrush(){	jqScribbleBrush.prototype.init = function(context, brushSize, brushColor) {		this.context = context;		this.context.globalCompositeOperation = 'source-over';		this.brushSize = brushSize;		this.brushColor = brushColor;		this.drawing = false;		this.prevX = null; 		this.prevY = null;	}	jqScribbleBrush.prototype.update = function(options) {		if(options.brushSize)this.brushSize = options.brushSize;		if(options.brushColor)this.brushColor = options.brushColor;	}	jqScribbleBrush.prototype.strokeBegin = function(x,y){};	jqScribbleBrush.prototype.strokeMove = function(x,y){};	jqScribbleBrush.prototype.strokeEnd = function(){};}//All brushes should inherit from the Brush interfaceBasicBrush.prototype = new jqScribbleBrush;function BasicBrush() {	BasicBrush.prototype.strokeBegin = function(x,y) {    this.drawing = true;    this.context.lineWidth = this.brushSize;		this.context.strokeStyle = this.brushColor;		this.context.lineCap = "round";    this.prevX = x;    this.prevY = y;	}		BasicBrush.prototype.strokeMove = function(x,y) {    if(this.drawing) {			this.context.beginPath();			this.context.moveTo(this.prevX, this.prevY);			this.context.lineTo(x, y);			this.context.stroke();			this.context.closePath();    }        this.prevX = x;    this.prevY = y;	};		BasicBrush.prototype.strokeEnd = function(ev) {    this.drawing = false;	}}function BasicCanvasSave(imageData){window.open(imageData,'My Image');}(function($){		var settings = {		width:				300,		height: 			250,		backgroundImage:	false,		backgroundImageX: 	0,		backgroundImageY: 	0,		saveMimeType: 		"image/png",		saveFunction: 		BasicCanvasSave,		brush:				BasicBrush,		brushSize:			2,		brushColor:			"rgb(0,0,0)"	};		var brush = null;	var canvas = null;	var canvasID = null;	var context = null;		function addImage()	{		var img = new Image();		img.src = settings.backgroundImage;		img.onload = function(){context.drawImage(img, settings.backgroundImageX, settings.backgroundImageY);}	}		$.fn.jqScribble = function(options) 	{		$.extend(settings, options);				//The canvas will take the inner dimensions 		//of the element it will be attached to, or		//the settings value if the dims aren't large		//enough to make a valid drawing area.		var width = this.innerWidth();		var height = this.innerHeight();		if(width < 2)		{			this.css("width", settings.width);			width = settings.width;		}		if(height < 2)		{			this.css("height", settings.height);			height = settings.height;		}				canvas = document.createElement("canvas");				$(canvas).attr('width',width);		$(canvas).attr('height',height);		$(canvas).attr('id',this.attr('id')+'-canvas');				this.append(canvas);				if($.browser.msie){  		canvas = G_vmlCanvasManager.initElement(canvas);  	};				context = canvas.getContext("2d");				if(settings.backgroundImage)addImage();				canvasID = this.attr('id')+'-canvas';				brush = new settings.brush();		brush.init(context, settings.brushSize, settings.brushColor);				var self = this;				if(brush.strokeBegin && brush.strokeMove && brush.strokeEnd)		{			// Mouse based interface			$(canvas).bind('mousedown', function(ev) {				ev.preventDefault();								var x, y;				x = ev.pageX - $(canvas).offset().left;				y = ev.pageY - $(canvas).offset().top;								brush.strokeBegin(x,y);			});						$(canvas).bind('mousemove', function(ev) {				var x, y;				x = ev.pageX - $(canvas).offset().left;				y = ev.pageY - $(canvas).offset().top;								brush.strokeMove(x,y);			});						$(canvas).bind('mouseup', function(ev) {				brush.strokeEnd();			});						$('body').bind('mouseup', function(ev) {				brush.strokeEnd();			});						// Touch screen based interface			if('ontouchstart' in canvas) {				canvas.addEventListener("touchstart", function(ev) {					var o = self.offset();					ev.preventDefault();					if(ev.touches.length == 1)brush.strokeBegin(ev.touches[0].pageX-o.left, ev.touches[0].pageY-o.top);				}, false);			}			if('ontouchmove' in canvas) {				canvas.addEventListener("touchmove",  function(ev) {					var o = self.offset();					ev.preventDefault();					if(ev.touches.length == 1)brush.strokeMove(ev.touches[0].pageX-o.left, ev.touches[0].pageY-o.top);				});			}			if('ontouchend' in canvas) {				canvas.addEventListener("touchend",   function(ev) {					ev.preventDefault();					if(ev.touches.length == 0)brush.strokeEnd();				});			}		}	};		$.fn.jqScribble.update = function(options)	{		var newImg = !!options.backgroundImage;		$.extend(settings, options);				if(newImg)addImage();		brush.init(context, settings.brushSize, settings.brushColor);	}		$.fn.jqScribble.clear = function(){context.clearRect(0, 0, canvas.width, canvas.height);}	$.fn.jqScribble.save = function(){		var imageData = null;		if(canvas.toDataURL) {			imageData = canvas.toDataURL(settings.saveMimeType);		} else {			imageData = toBitmapURL(canvas);		}				settings.saveFunction(imageData);	}})(jQuery);var toBitmapURL = (function ($fromCharCode, FF, MAX_LENGTH) {        /**     * (C) WebReflection - Mit Style License     *      given a canvas, returns BMP 32bit with alpha channel data uri representation     *     * Why ?     *      because many canvas implementation may not support toDataURL     *      ( HTMLCanvasElement.prototype.toDataURL || HTMLCanvasElement.prototype.toDataURL = function () {return toBitmapURL(this)}; )     *     * I mean ... Why BMP 32 rather than PNG ?!!!     *      because JavaScript size matter as well as computation time.     *      PNG requires DEFLATE compression and multiple pass over the data.     *      BMP is straight forward     *     * Fine, but which browser supports BMP in 2011 ?     *      pretty much all of them, except some version of Chrome. Safari and Webkit are fine as well as Firefox, Opera and of course IE     *     * Sure, but why on earth should I use BMP as data uri ?     *      this method is about creation of canvas snapshots. If toDataURL is not presemt     *      there is still a way to create a portable, NOT COMPRESSED, bitmap image     *      that could be optionally sent to the server and at that point converted into proper PNG     *      Bitmap format was fast enough to parse (on mobile as well) and it was RGBA compatible plus widely supported.     *     * I think this was a wasteof time     *      well, if you still think so, I can say that was actually fun to create a proper     *      32 bit image format via JavaScript on the fly.     *      However, please share your own toDataURL version with full mime type support in JavaScript :P     *      Moreover, have you ever tried to use native toDataURL("image/bmp") ?     *      Most likely you gonna have max 24bit bitmap with all alpha channel info lost.     */        function fromCharCode(code) {        for (var            result = [],            i = 0,            length = code.length;            i < length; i += MAX_LENGTH        ) {            result.push($fromCharCode.apply(null, code.slice(i, i + MAX_LENGTH)));        }        return result.join("");    }        function numberToInvertedBytes(number) {        return [            number & FF,            (number >> 8) & FF,            (number >> 16) & FF,            (number >> 24) & FF        ];    }        function swapAndInvertY(data, width, height) {        /**         * Bitmap pixels array is stored "pseudo inverted"         * RGBA => BGRA (read as Alpha + RGB)         * in few words this canvas pixels array         * [         *   0, 1,  2,  3,  4,  5,  6,  7,         *   8, 9, 10, 11, 12, 13, 14, 15         * ]         * is stored as bitmap one like         * [         *   10, 9, 8, 11, 14, 13, 12, 15,         *   2, 1, 0,  3,  6,  5,  4,  7         * ]         */        for (var            i, j, x0, x1, y0, y1,            sizeX = 4 * width,            sizeY = height - 1,            result = [];            height--;        ) {            y0 = sizeX * (sizeY - height);            y1 = sizeX * height;            for (i = 0; i < width; i++) {                j = i * 4;                x0 = y0 + j;                x1 = y1 + j;                result[x0] = data[x1 + 2];                result[x0 + 1] = data[x1 + 1];                result[x0 + 2] = data[x1];                result[x0 + 3] = data[x1 + 3];            }        }        return result;    }        function toBitmapURL(canvas) {        var            width = canvas.width,            height = canvas.height,            header = [].concat(                numberToInvertedBytes(width),                numberToInvertedBytes(height),                1, 0,                32, 0,                3, 0, 0, 0,                numberToInvertedBytes(                    width * height * 4                ),                19, 11, 0, 0,                19, 11, 0, 0,                0, 0, 0, 0,                0, 0, 0, 0,                0, 0, FF, 0,                0, FF, 0, 0,                FF, 0, 0, 0,                0, 0, 0, FF,                32, 110, 105, 87            ),            data = swapAndInvertY(                canvas.getContext("2d").getImageData(                    0, 0, width, height                ).data,                width,                height            ),            offset        ;        header = numberToInvertedBytes(header.length).concat(header);        offset = 14 + header.length;        return "data:image/bmp;base64," + btoa(fromCharCode(            [66, 77].concat(                numberToInvertedBytes(offset + data.length),                0, 0, 0, 0,                numberToInvertedBytes(offset),                header,                data            )        ));    }        return toBitmapURL;    }(String.fromCharCode, 0xFF, 0x7FFF));
