<div class="canvas" name="Invert">
<img src="Image1.png" id="Image1.png" style="display:none;">
<img src="Image2.png" id="Image2.png" style="display:none;">
<canvas id="Invert" width="150" height="142"></canvas>
<script id="Invert.js">
document.getElementById('Image1.png').addEventListener('load', Invert);
function Invert() {
	var canvas = document.getElementById('Invert');
	var ctx    = canvas.getContext('2d');
	var img    = document.getElementById('Image1.png');
	var w      = canvas.width  = img.naturalWidth;
	var h      = canvas.height = img.naturalHeight;
	ctx.drawImage(img, 0, 0);
	var imgdt  = ctx.getImageData(0, 0, w, h);
	var a      = imgdt.data;
	for (var k=0; k<a.length; k+=4) {
		a[k  ] = 255 - a[k  ];
		a[k+1] = 255 - a[k+1];
		a[k+2] = 255 - a[k+2];
		a[k+3] = 255;
	}
	ctx.putImageData(imgdt, 0, 0);
}
</script>
</div><div class="canvas" name="Grayscale">
<canvas id="Grayscale" width="150" height="142"></canvas>
<script id="Grayscale.js">
document.getElementById('Image1.png').addEventListener('load', Grayscale);
function Grayscale() {
	var canvas = document.getElementById('Grayscale');
	var ctx    = canvas.getContext('2d');
	var img    = document.getElementById('Image1.png');
	var w      = canvas.width  = img.naturalWidth;
	var h      = canvas.height = img.naturalHeight;
	ctx.drawImage(img, 0, 0);
	var imgdt  = ctx.getImageData(0, 0, w, h);
	var a      = imgdt.data;
	for (var k=0; k<a.length; k+=4) {
		var p = Math.floor((a[k  ] + a[k+1] + a[k+2]) / 3);
		a[k  ] = a[k+1] = a[k+2] = p;
		a[k+3] = 255;
	}
	ctx.putImageData(imgdt, 0, 0);
}
</script>
</div><div class="canvas" name="Smooth">
<canvas id="Smooth" width="150" height="142"></canvas>
<script id="Smooth.js">
document.getElementById('Image1.png').addEventListener('load', Smooth);
function Smooth() {
	var canvas = document.getElementById('Smooth');
	var ctx    = canvas.getContext('2d');
	var img    = document.getElementById('Image1.png');
	var w      = canvas.width  = img.naturalWidth;
	var h      = canvas.height = img.naturalHeight;
	ctx.drawImage(img, 0, 0);
	var imgdt  = ctx.getImageData(0, 0, w, h);
	var a      = imgdt.data;
	var imgdt2 = ctx.createImageData(w, h);
	var b      = imgdt2.data;

	for (var k=0; k<a.length; k+=4) {
		b[k  ] = avg(k,0);
		b[k+1] = avg(k,1);
		b[k+2] = avg(k,2);
		b[k+3] = 255;
	}

	function avg(k,channel) {
		var dx = [-2,-1,0,1,2,-2,-1,0,1,2,-2,-1,0,1,2,-2,-1,0,1,2,-2,-1,0,1,2];
		var dy = [-2,-2,-2,-2,-2,-1,-1,-1,-1,-1,0,0,0,0,0,1,1,1,1,1,2,2,2,2,2];
		var p = 0;
		for (var i=0; i<25; ++i) {
			var y = Math.floor(k/4/w)+dy[i];
			y = Math.min(y,h-1); y = Math.max(y,0);
			var x = k/4%w+dx[i];
			x = Math.min(x,w-1); x = Math.max(x,0);
			p += a[(y*w+x)*4 + channel];
		}
		return Math.floor(p / 25);
	}

	ctx.putImageData(imgdt2, 0, 0);
}
</script>
</div><div class="canvas" name="ImageInterpolation">
<canvas id="ImageInterpolation1" width="150" height="142"></canvas>
<canvas id="ImageInterpolation1b" width="150" height="142"></canvas>
<script id="ImageInterpolation.js">
document.getElementById('Image1.png').addEventListener('load', ImageInterpolation);

function ImageInterpolation() {
	var img    = document.getElementById('Image1.png');
	var w      = img.naturalWidth;
	var h      = img.naturalHeight;

	var p1 = [{x:20,y:20},{x:100,y:120},{x:140,y:10}];
	ImageInterpolationInit('ImageInterpolation1', p1, TriangularCoordinates);

	var p2 = [{x:10,y:10},{x:w-10,y:10},{x:w-10,y:h-10},{x:10,y:h-10}];
	ImageInterpolationInit('ImageInterpolation2', p2, QuadrilateralCoordinates);
}

function ImageInterpolationInit(name, point, transform) {
	var canvas1 = document.getElementById(name);
	var ctx1    = canvas1.getContext('2d');
	var canvas2 = document.getElementById(name + 'b');
	var ctx2    = canvas2.getContext('2d');

	var draw = function(w,h) {
		c2.a     = c2.imgdt.data;
		ImageInterpolationAlgo(h, w, c1.a, c2.a, c1.point, c2.point, transform);
		ctx1.putImageData(c1.imgdt, 0, 0);
		c1.drawpoint();
		ctx2.putImageData(c2.imgdt, 0, 0);
		c2.drawpoint();
	};

	var c1 = new ImageInterpolationMain(canvas1, ctx1, point.slice(), draw);
	var c2 = new ImageInterpolationMain(canvas2, ctx2, point.slice(), draw);
}

function ImageInterpolationMain(canvas, ctx, point, draw) {
	var img    = document.getElementById('Image1.png');
	var w      = canvas.width  = img.naturalWidth;
	var h      = canvas.height = img.naturalHeight;
	ctx.drawImage(img, 0, 0);
	var imgdt  = this.imgdt = ctx.getImageData(0, 0, w, h);
	var a      = this.a     = imgdt.data;

	var drawpoint = this.drawpoint = function() {
		var n = point.length;
		// line
		ctx.lineWidth = 1;
		ctx.strokeStyle = "brown";
		for (var i=0; i<n; ++i) {
			ctx.beginPath();
			ctx.moveTo(point[i].x, point[i].y);
			ctx.lineTo(point[(i+1)%n].x, point[(i+1)%n].y);
			ctx.closePath();
			ctx.stroke();
		}
		// point
		ctx.lineWidth = 2;
		ctx.strokeStyle = "black";
		ctx.fillStyle = "brown";
		for (var i=0; i<n; ++i) {
			ctx.beginPath();
			ctx.arc(point[i].x, point[i].y, 5, 0, 2 * Math.PI);
			ctx.fill();
			ctx.stroke();
		}
	};
	drawpoint();

	// control points
	this.point = point;
	var hitpoint = -1;
	var hit = function(p, mouse) {
		return p.x - 7 <= mouse.x && p.x + 7 >= mouse.x
			&& p.y - 7 <= mouse.y && p.y + 7 >= mouse.y;
	};
	var coordinate = function(x, y) {
		var s = 2;
		return {x: Math.round(x/s)*s, y: Math.round(y/s)*s};
	};

	// mouse control
	canvas.onmousedown = function(e){
		var mouse = coordinate(e.offsetX, e.offsetY);
		hitpoint = -1;
		for (var i=0; i<point.length; ++i)
			if (hit(point[i], mouse))
				hitpoint = i;
	};

	canvas.onmousemove = function(e){
		if (hitpoint == -1) return;
		point[hitpoint] = coordinate(e.offsetX, e.offsetY);
		draw(w,h);
	};

	canvas.onmouseup = canvas.onmouseout = function(e){
		hitpoint = -1;
	};

	ctx.font = "16pt Arial";
	ctx.textBaseline = "middle";
	ctx.textAlign = "center";
	ctx.fillStyle = "rgb(0,127,0)";
	ctx.fillText("Drag Point !", w/2, h/2);
}

// fill b
function ImageInterpolationAlgo(X, Y, a, b, p1, p2, transform) {
	var idx     = function(x,y){return x*Y+y;};
	var onboard = function(x,y){return x>=0 && x<X && y>=0 && y<Y;};

	for (var x=0; x<X; ++x)
		for (var y=0; y<Y; ++y) {
			var [ty, tx] = transform(y, x, p1, p2);
			var i = Math.round(tx);
			var j = Math.round(ty);
			if (onboard(i,j)) {
				var bi = idx(x,y)*4;
				var ai = idx(i,j)*4;
				b[bi+0] = a[ai+0]; b[bi+1] = a[ai+1]; b[bi+2] = a[ai+2];
				b[bi+3] = 255;
			} else {
				var bi = idx(x,y)*4;
				b[bi+0] = b[bi+1] = b[bi+2] = b[bi+3] = 0;
			}
		}
}

function TriangularCoordinates(x, y, p1, p2) {
	var forward = function(u,v,w,p) {
		var x = u * p[0].x + v * p[1].x + w * p[2].x;
		var y = u * p[0].y + v * p[1].y + w * p[2].y;
		return [x,y];
	};
	var backward = function(x,y,p) {
		var x0 = p[1].x - p[0].x;   var x1 = p[2].x - p[0].x;   var x2 = x - p[0].x;
		var y0 = p[1].y - p[0].y;   var y1 = p[2].y - p[0].y;   var y2 = y - p[0].y;
		var dot = function(x1,y1,x2,y2) {return x1*x2+y1*y2;};
		var d00 = dot(x0,y0,x0,y0);
		var d01 = dot(x0,y0,x1,y1);
		var d11 = dot(x1,y1,x1,y1);
		var d20 = dot(x2,y2,x0,y0);
		var d21 = dot(x2,y2,x1,y1);
		var d = d00 * d11 - d01 * d01;
		var v = (d11 * d20 - d01 * d21) / d;
		var w = (d00 * d21 - d01 * d20) / d;
		var u = 1 - v - w;
		return [u,v,w];
	};

	var [u,v,w] = backward(x, y, p2);
	return [x,y] = forward(u, v, w, p1);
}

function QuadrilateralCoordinates(x, y, p1, p2) {
	var forward = function(s,t,p) {
		var x = p[0].x + (p[1].x-p[0].x)*s + (p[3].x-p[0].x)*t + (p[0].x-p[1].x+p[2].x-p[3].x)*s*t;
		var y = p[0].y + (p[1].y-p[0].y)*s + (p[3].y-p[0].y)*t + (p[0].y-p[1].y+p[2].y-p[3].y)*s*t;
		return [x,y];
	};
	var backward = function(x,y,p) {
		var a = [p[0].x, p[1].x-p[0].x, p[3].x-p[0].x, p[0].x-p[1].x+p[2].x-p[3].x];
		var b = [p[0].y, p[1].y-p[0].y, p[3].y-p[0].y, p[0].y-p[1].y+p[2].y-p[3].y];
		var aa = a[3]*b[2] - a[2]*b[3];
		var bb = a[3]*b[0] - a[0]*b[3] + a[1]*b[2] - a[2]*b[1] + x*b[3] - y*a[3];
		var cc = a[1]*b[0] - a[0]*b[1] + x*b[1] - y*a[1];
		var det = Math.sqrt(Math.max(0, bb*bb - 4*aa*cc));
		var t = (aa == 0) ? -cc/bb : (-bb+det)/(2*aa);
		var s = (x-a[0]-a[2]*t)/(a[1]+a[3]*t);
		return [s,t];
	};

	var [s,t] = backward(x, y, p2);
	return [x,y] = forward(s, t, p1);
}
</script>
</div><div class="canvas" name="ImageWarping">
<img src="ImageWarping1.png">
<canvas id="ImageWarping" width="150" height="142"></canvas>
<script id="ImageWarping.js">
document.getElementById('Image1.png').addEventListener('load', ImageWarping);

function ImageWarping() {
	var canvas = document.getElementById('ImageWarping');
	var ctx    = canvas.getContext('2d');
	var img    = document.getElementById('Image1.png');
	var w      = canvas.width  = img.naturalWidth;
	var h      = canvas.height = img.naturalHeight;
	ctx.drawImage(img, 0, 0);
	var imgdt  = ctx.getImageData(0, 0, w, h);
	var a      = new Uint8ClampedArray(imgdt.data);

	ctx.font = "16pt Arial";
	ctx.textBaseline = "middle";
	ctx.textAlign = "center";
	ctx.fillStyle = "rgb(0,127,0)";
	ctx.fillText("Drag Points !", w/2, h/2);

	// control points
	var n = 4;	// 4x4 points
	var point = [
		{x:0,y:0},{x:w,y:0},{x:w,y:h},{x:0,y:h},
		{x:0,y:0},{x:w,y:0},{x:w,y:h},{x:0,y:h},
		{x:0,y:0},{x:w,y:0},{x:w,y:h},{x:0,y:h},
		{x:0,y:0},{x:w,y:0},{x:w,y:h},{x:0,y:h},
	];
	for (var i=0; i<n; ++i) for (var j=0; j<n; ++j) {
		var k = i * n + j;
		point[k].x = Math.floor(w / (n-1)) * i;
		point[k].y = Math.floor(h / (n-1)) * j;
	}
	var hitpoint = -1;
	var hit = function(p, mouse) {
		return p.x - 7 <= mouse.x && p.x + 7 >= mouse.x
			&& p.y - 7 <= mouse.y && p.y + 7 >= mouse.y;
	};
	var coordinate = function(x, y) {
		var s = 2;
		return {x: Math.round(x/s)*s, y: Math.round(y/s)*s};
	};
	var drawpoint = function() {
		// line
		ctx.lineWidth = 1;
		ctx.strokeStyle = "rgba(198,0,0,0.5)";	// brown
		for (var i=0; i<4; ++i) for (var j=0; j<3; ++j) {
			ctx.beginPath();
			ctx.moveTo(point[i*n+j].x, point[i*n+j].y);
			ctx.lineTo(point[i*n+j+1].x, point[i*n+j+1].y);
			ctx.closePath();
			ctx.stroke();
		}
		for (var j=0; j<4; ++j) for (var i=0; i<3; ++i) {
			ctx.beginPath();
			ctx.moveTo(point[i*n+j].x, point[i*n+j].y);
			ctx.lineTo(point[(i+1)*n+j].x, point[(i+1)*n+j].y);
			ctx.closePath();
			ctx.stroke();
		}
		// point
		ctx.lineWidth = 2;
		ctx.strokeStyle = "rgba(0,0,0,0.5)";	// black
		ctx.fillStyle = "rgba(198,0,0,0.3)";	// brown
		for (var i=0; i<n*n; ++i) {
			ctx.beginPath();
			ctx.arc(point[i].x, point[i].y, 5, 0, 2 * Math.PI);
			ctx.fill();
			ctx.stroke();
		}
	};
	drawpoint();

	// mouse control
	canvas.onmousedown = function(e){
		var mouse = coordinate(e.offsetX, e.offsetY);
		hitpoint = -1;
		for (var i=0; i<n*n; ++i)
			if (hit(point[i], mouse))
				hitpoint = i;
	};

	canvas.onmousemove = function(e){
		if (hitpoint == -1) return;
		point[hitpoint] = coordinate(e.offsetX, e.offsetY);
		// draw
		var b = imgdt.data;
		ImageWarpingAlgo(h, w, a, b, point, n);	// draw b
		ctx.putImageData(imgdt, 0, 0);			// draw b
		drawpoint();
	};

	canvas.onmouseup = canvas.onmouseout = function(e){
		hitpoint = -1;
	};
}

function ImageWarpingAlgo(X, Y, a, b, p, n) {
	var idx     = function(x,y){return x*Y+y;};
	var onboard = function(x,y){return x>=0 && x<X && y>=0 && y<Y;};

	// quadrilateral interpolation
	var inverse = function(x,y,p) {
		var a = [p[0].x, p[1].x-p[0].x, p[3].x-p[0].x, p[0].x-p[1].x+p[2].x-p[3].x];
		var b = [p[0].y, p[1].y-p[0].y, p[3].y-p[0].y, p[0].y-p[1].y+p[2].y-p[3].y];
		var aa = a[3]*b[2] - a[2]*b[3];
		var bb = a[3]*b[0] - a[0]*b[3] + a[1]*b[2] - a[2]*b[1] + x*b[3] - y*a[3];
		var cc = a[1]*b[0] - a[0]*b[1] + x*b[1] - y*a[1];
		var det = Math.sqrt(Math.max(0, bb*bb - 4*aa*cc));
		var t = (aa == 0) ? -cc/bb : (-bb+det)/(2*aa);
		var s = (x-a[0]-a[2]*t)/(a[1]+a[3]*t);
		return {x:s, y:t};
	};

	// point in mesh
	var position = function(x,y) {
		var o = {x:x, y:y};
		var cross = function(o,a,b) {return (a.x-o.x)*(b.y-o.y)-(b.x-o.x)*(a.y-o.y);};
		for (var i=0; i<n-1; ++i)
			for (var j=0; j<n-1; ++j) {
				var q = [p[i*n+j], p[(i+1)*n+j], p[(i+1)*n+(j+1)], p[i*n+(j+1)]];
				if (cross(o,q[0],q[1]) < 0) continue;
				if (cross(o,q[1],q[2]) < 0) continue;
				if (cross(o,q[2],q[3]) < 0) continue;
				if (cross(o,q[3],q[0]) < 0) continue;
				return {q:q, x:i, y:j};
			}
		return {q:[p[0], p[0], p[0], p[0]], x:0, y:0};
	}

	// fill b
	for (var x=0; x<X; ++x)
		for (var y=0; y<Y; ++y) {
			var pos = position(y,x);
			var t = inverse(y,x,pos.q);
			var i = Math.round(X/(n-1) * (pos.y + t.y));
			var j = Math.round(Y/(n-1) * (pos.x + t.x));
			if (onboard(i,j)) {
				var bi = idx(x,y)*4;
				var ai = idx(i,j)*4;
				b[bi+0] = a[ai+0]; b[bi+1] = a[ai+1]; b[bi+2] = a[ai+2];
				b[bi+3] = 255;
			} else {
				var bi = idx(x,y)*4;
				b[bi+0] = b[bi+1] = b[bi+2] = b[bi+3] = 0;
			}
		}
}
</script>
</div><div class="canvas" name="ImageStylization">
<canvas id="ImageStylization" width="150" height="142"></canvas>
<script id="ImageStylization.js">
document.getElementById('Image1.png').addEventListener('load', ImageStylization);

function ImageStylization() {
	var canvas = document.getElementById('ImageStylization');
	var ctx    = canvas.getContext('2d');
	var img    = document.getElementById('Image1.png');
	var w      = canvas.width  = img.naturalWidth;
	var h      = canvas.height = img.naturalHeight;
	ctx.drawImage(img, 0, 0);
	var imgdt  = ctx.getImageData(0, 0, w, h);
	var a      = new Uint8ClampedArray(imgdt.data);

	ctx.font = "26pt Arial";
	ctx.textBaseline = "middle";
	ctx.textAlign = "center";
	ctx.strokeStyle = "rgb(0,127,0)";
	ctx.strokeText("Click Me !", w/2, h/2);

	var id = 0, on = 0;
	canvas.onclick = function(){
		clearInterval(id);
		if (on = !on)	id = setInterval(function(){ImageStylizationDraw(ctx, imgdt, w, h, a);}, 200);
		else			ImageStylization();
	}
};

function ImageStylizationDraw(ctx, imgdt, w, h, a) {
	var b = imgdt.data;
	b.set(a);

	var strokegap = 3;
	var strokelength = 3;
	var strokewidth  = 3;

	var idx = function(i,j){return (i * w + j) * 4;};
	for (var i=0; i<h; i+=strokegap)
		for (var j=0; j<w; j+=strokegap) {
			var p1 = idx(i, j);
			var ii = i, jj = j;
			for (var k=0; k<strokelength; k++) {
				var d = [[0,1],[1,0],[1,1]][Math.floor(Math.random() * 3)];
				ii += d[0]; jj += d[1];
				if (!(ii < h && jj < w)) break;
				for (var l=0; l<strokewidth; l++) {
					var p2 = idx(ii, jj+l);
					b[p2+0] = a[p1+0];
					b[p2+1] = a[p1+1];
					b[p2+2] = a[p1+2];
					b[p2+3] = a[p1+3];
				}
			}
		}

	ctx.putImageData(imgdt, 0, 0);
}
</script>
</div><div class="canvas" name="ImageBlending">
<img src="ImageBlending1.jpg" id="ImageBlending1.jpg" style="display: none;">
<img src="ImageBlending2.jpg" id="ImageBlending2.jpg" style="display: none;">
<canvas id="ImageBlending" width="200" height="200"></canvas>
<script id="loader.js">
function Loader(main, ...files) {
	var n = 0;
	function onload() {if (++n == files.length) main();}

	for (let file of files) {
		var element = document.getElementById(file);
		element.addEventListener('load', onload);
	}
}
</script>
<script id="ImageBlending.js">
new Loader(ImageBlending, 'ImageBlending1.jpg', 'ImageBlending2.jpg');

function ImageBlending() {
	var canvas = document.getElementById('ImageBlending');
	var ctx    = canvas.getContext('2d');
	// background
	var img1   = document.getElementById('ImageBlending1.jpg');
	var w1     = canvas.width  = img1.naturalWidth;
	var h1     = canvas.height = img1.naturalHeight;
	ctx.drawImage(img1, 0, 0);
	var imgdt1 = ctx.getImageData(0, 0, w1, h1);
	var a      = new Uint8ClampedArray(imgdt1.data);
	// pattern
	var img2   = document.getElementById('ImageBlending2.jpg');
	var w2     = img2.naturalWidth;
	var h2     = img2.naturalHeight;
	var x      = w1/2 - w2/2;
	var y      = h2/2;
	ctx.drawImage(img2, x, y);
	var imgdt2 = ctx.getImageData(x, y, w2, h2);

	// index
	function ɪ1(i, j, k) {
		if (i < 0) i = 0; if (i >= w1) i = w1-1;
		if (j < 0) j = 0; if (j >= h1) j = h1-1;
		return (j * w1 + i) * 4 + k;
	}
	function ɪ2(i, j, k) {
		if (i < 0) i = 0; if (i >= w2) i = w2-1;
		if (j < 0) j = 0; if (j >= h2) j = h2-1;
		return (j * w2 + i) * 4 + k;
	}
	function ɪ1s(i, j, k) {return ɪ1(i+x, j+y, k);}

	// divergence
	var div1   = new Int16Array(imgdt1.data);
	var div2   = new Int16Array(imgdt2.data);
	var div12  = new Int16Array(imgdt2.data);
	divergence(imgdt1.data, div1, w1, h1, ɪ1);
	divergence(imgdt2.data, div2, w2, h2, ɪ2);
//	remix(div, div2, div12, w1, h1, w2, h2, x, y);

	function divergence(a, b, w, h, ɪ) {
		for (let j=0; j<h; ++j)
			for (let i=0; i<w; ++i)
				for (let k=0; k<3; ++k)
					b[ɪ(i,j,k)] = a[ɪ(i-1,j,k)] + a[ɪ(i+1,j,k)]
								+ a[ɪ(i,j-1,k)] + a[ɪ(i,j+1,k)]
								- a[ɪ(i,j,k)] * 4;
	}

	function remix(a1, a2, b, w1, h1, w2, h2, x, y) {
		for (let j=0; j<h2; ++j)
			for (let i=0; i<w2; ++i)
				for (let k=0; k<3; ++k) {
					var n1 = Math.abs(a1[ɪ1s(i,j,k)]) * 0.3;
					var n2 = Math.abs(a2[ɪ2(i,j,k)]);
					b[ɪ2(i,j,k)] = (n1 > n2) ? a1[ɪ1s(i,j,k)] : a2[ɪ2(i,j,k)];
				}
	}

	function poisson(a, b, w1, h1, w2, h2, x, y) {
		for (let t=0; t<4; ++t)
			for (let j=0; j<h2; ++j)
				for (let i=0; i<w2; ++i)
					for (let k=0; k<3; ++k)
						a[ɪ1s(i,j,k)] = ( a[ɪ1s(i-1,j,k)] + a[ɪ1s(i+1,j,k)]
										+ a[ɪ1s(i,j-1,k)] + a[ɪ1s(i,j+1,k)]
										- b[ɪ2(i,j,k)] ) / 4;
	}

	ctx.font = "16pt Arial";
	ctx.textBaseline = "middle";
	ctx.textAlign = "center";
	ctx.fillStyle = "rgb(0,31,0)";
	ctx.fillText("Drag Eye !", w1/2, h1/2);

	function draw() {
		remix(div1, div2, div12, w1, h1, w2, h2, x, y);
		imgdt1.data.set(a);
		poisson(imgdt1.data, div12, w1, h1, w2, h2, x, y);
		ctx.putImageData(imgdt1, 0, 0);
	}

	// mouse control
	var drag = false;
	var ox = 0, oy = 0;
	canvas.onmousedown = function(e){
		drag = (e.offsetX >= x && e.offsetX < x + w2
			 && e.offsetY >= y && e.offsetY < y + h2);
		ox = e.offsetX;
		oy = e.offsetY;
	};

	canvas.onmousemove = function(e){
		if (!drag) return;
		x += e.offsetX - ox; ox = e.offsetX;
		y += e.offsetY - oy; oy = e.offsetY;
		x = Math.max(x, 0);
		x = Math.min(x, w1 - w2);
		y = Math.max(y, 0);
		y = Math.min(y, h1 - h2);
		requestAnimationFrame(draw, canvas);
	};

	canvas.onmouseup = canvas.onmouseout = function(e){
		drag = false;
	};
}
</script>
</div><div class="canvas" name="SeamCarving">
<canvas id="SeamCarving" width="274" height="186"></canvas>
<img src="BroadwayTowerSeamCarvingA.png" id="BroadwayTowerSeamCarvingA.png" style="display:none;">
<script id="SeamCarving.js">
document.getElementById('BroadwayTowerSeamCarvingA.png').addEventListener('load', SeamCarving);

function SeamCarving() {
	var canvas = document.getElementById('SeamCarving');
	var ctx    = canvas.getContext('2d');
	var img    = document.getElementById('BroadwayTowerSeamCarvingA.png');
	var w      = canvas.width  = img.naturalWidth;
	var h      = canvas.height = img.naturalHeight;
	ctx.drawImage(img, 0, 0);

	var imgdt  = ctx.getImageData(0, 0, w, h);
	var a      = imgdt.data;
	var origin = new Uint8ClampedArray(a);

	var grad   = new Int16Array(w*h);
	var sum    = new Int32Array(w*h);
	var path   = new Int8Array(w*h);
	var tw     = 0;

	canvas.onmousemove = draw;
	draw();

	function draw() {
		if (tw < 2) {
			a.set(origin);
			tw = w;
		} else {
			carve(a, tw, h, true);
			tw--;
		}
		sobel(a, tw, h);
		seam(a, grad, tw, h);
		carve(a, tw, h, false);
//		imgdt.data.set(a);
		ctx.putImageData(imgdt, 0, 0);
	}

	function onboard(x,y) {return x>=0 && x<tw && y>=0 && y<h;}
	function index(x,y) {return (y*w + x);}
	function ɪ(x,y,c) {return (y*w + x)*4 + c;}

	function sobel(a, w, h) {
		var dx = [-1,0,1,-1,0,1,-1,0,1];
		var dy = [-1,-1,-1,0,0,0,1,1,1];
		var mx = [-1,0,1,-2,0,2,-1,0,1];
		var my = [-1,-2,-1,0,0,0,1,2,1];
		for (var y=0; y<h; ++y)
			for (var x=0; x<w; ++x) {
				var sum = 0
				for (var c=0; c<3; ++c) {
					var sx = 0, sy = 0;
					for (var i=0; i<9; ++i) {
						var value = onboard(x+dx[i],y+dy[i])
						          ? a[ɪ(x+dx[i],y+dy[i],c)]
						          : a[ɪ(x,y,c)];
						sx += value * mx[i];
						sy += value * my[i];
					}
					sum += Math.abs(sx) + Math.abs(sy);
//					b[ɪ(x,y,c)] = Math.abs(sx) + Math.abs(sy);
				}
				grad[index(x,y)] = sum;
//				b[ɪ(x,y,3)] = 255;
			}
		return grad;
	}

	function seam(a, c, w, h) {
		var dx = [-1,0,1];
		for (var x=0, y=0; x<w; ++x) {
			sum[index(x,y)] = c[index(x,y)];
			path[index(x,y)] = 0;
		}
		for (var y=1; y<h; ++y)
			for (var x=0; x<w; ++x) {
				var mini = 100000000;
				for (var i=0; i<dx.length; ++i)
					if (onboard(x+dx[i], y-1))
						if (sum[index(x+dx[i],y-1)] < mini) {
							mini = sum[index(x+dx[i],y-1)];
							path[index(x,y)] = i;
						}
				sum[index(x,y)] = mini + c[index(x,y)];
			}
	}

	function carve(a, w, h, remove) {
		var dx = [-1,0,1];
		var mini = 100000000, pos = 0;
		for (var x=0; x<w; ++x)
			if (sum[index(x,h-1)] < mini) {
				mini = sum[index(x,h-1)];
				pos = x;
			}

		for (var y=h-1; y>=0; --y) {
			if (!onboard(pos,y)) alert(pos);
			if (remove) {
				// remove seam
				for (var x=pos; x<w-1; ++x)
					for (var c=0; c<4; ++c)
						a[ɪ(x,y,c)] = a[ɪ(x+1,y,c)];
				for (var c=0; c<4; ++c)
					a[ɪ(w-1,y,c)] = 255;
			} else {
				// draw seam
				a[ɪ(pos,y,0)] = 255;
				a[ɪ(pos,y,1)] = 0;
				a[ɪ(pos,y,2)] = 0;
			}
			pos += dx[path[index(pos,y)]];
		}
		return a;
	}
}
</script>
</div><div class="canvas" name="ImageInpainting">
<img src="Image1.png" width="150" height="142">
<canvas id="ImageInpainting" width="150" height="142"></canvas>
<script id="ImageInpainting.js">
document.getElementById('Image1.png').addEventListener('load', ImageInpainting);

function ImageInpainting() {
	var canvas = document.getElementById('ImageInpainting');
	var ctx    = canvas.getContext('2d');
	var img    = document.getElementById('Image1.png');
	var w      = canvas.width  = img.naturalWidth;
	var h      = canvas.height = img.naturalHeight;
	ctx.drawImage(img, 0, 0);
	var imgdt  = ctx.getImageData(0, 0, w, h);
	var a      = imgdt.data;

	ctx.font = "16pt Arial";
	ctx.textBaseline = "top";
	ctx.textAlign = "left";
	ctx.fillStyle = "rgb(0,127,0)";
	ctx.fillText("Erase Me !", 0, 0);

	// stroke style
	ctx.lineWidth = 5;
	ctx.lineJoin = 'round';
	ctx.lineCap = 'round';
	ctx.strokeStyle = 'black';

	// mouse control
	var mouse = {x: 0, y: 0};
	canvas.addEventListener('mousemove', function(e) {
		mouse.x = e.offsetX;
		mouse.y = e.offsetY;
	});
	canvas.onmousedown = function(e) {
		ctx.beginPath();
		ctx.moveTo(mouse.x, mouse.y);
		canvas.onmousemove = function() {
			ctx.lineTo(mouse.x, mouse.y);
			ctx.stroke();
		};
	};
	canvas.onmouseup = function() {
		canvas.onmousemove = function(e){return false;};
		var b = ctx.getImageData(0, 0, w, h).data;
		ImageInpaintingAlgo(h, w, a, b);	// draw a
		ctx.putImageData(imgdt, 0, 0);		// draw a
	};
}

function ImageInpaintingAlgo(X, Y, a, b) {
	var idx     = function(x,y){return x*Y+y;};
	var onboard = function(x,y){return x>=0 && x<X && y>=0 && y<Y;};
	var painted = function(x,y){return onboard(x,y) && m[idx(x,y)] == 0;};

	// extract mask
	var m       = new Array(X*Y);
	var d       = new Array(X*Y);
	for (var k=0; k<X*Y; k++) {
		m[k] = (b[k*4+0] == 0 && b[k*4+1] == 0 && b[k*4+2] == 0) ? 1 : 0;
		d[k] = (m[k] == 1) ? X+Y : 0;
	}

	// label setting algorithm: weight
	var dist = function(x,y) {
		var min = X + Y;
		for (var i=0; i<4; ++i) {
			var dx = [0,1,0,-1,0];
			var dy = [1,0,-1,0,1];
			var x1 = x + dx[i];         var x2 = x + dx[i+1];
			var y1 = y + dy[i];         var y2 = y + dy[i+1];
			var o1 = painted(x1, y1);   var o2 = painted(x2, y2);  
			var d1 = d[idx(x1,y1)];     var d2 = d[idx(x2,y2)];    
			if (o1)       min = Math.min(min, d1 + 1);
			if (o1 && o2) min = Math.min(min, (d1 + d2 + Math.sqrt(2-(d1-d2)*(d1-d2))) / 2);
		}
		return d[idx(x,y)] = Math.min(min, d[idx(x,y)]);
	};

	// inpaint a pixel
	var inpaint = function(x,y) {
		for (var c=0; c<3; ++c) {	// rgb channel
			var s = 0, sx = 0, sy = 0, wsum = 0;
			for (var i=0; i<8; ++i) {
				var dx = [-1,-1,-1,0,0,1,1,1];
				var dy = [-1,0,1,-1,1,-1,0,1];
				var xx = x + dx[i];
				var yy = y + dy[i];
				if (!painted(xx, yy)) continue;
				var nx = (painted(x+1,y) ? d[idx(x+1,y)] : d[idx(x,y)])
				       - (painted(x-1,y) ? d[idx(x-1,y)] : d[idx(x,y)]);
				var ny = (painted(x,y+1) ? d[idx(x,y+1)] : d[idx(x,y)])
				       - (painted(x,y-1) ? d[idx(x,y-1)] : d[idx(x,y)]);
				var gx = (painted(x+1,y) ? a[idx(x+1,y)*4+c] : a[idx(x,y)*4+c])
				       - (painted(x-1,y) ? a[idx(x-1,y)*4+c] : a[idx(x,y)*4+c]);
				var gy = (painted(x,y+1) ? a[idx(x,y+1)*4+c] : a[idx(x,y)*4+c])
				       - (painted(x,y-1) ? a[idx(x,y-1)*4+c] : a[idx(x,y)*4+c]);
				var norm = dx[i] * dx[i] + dy[i] * dy[i];
				var w = Math.abs(dx[i] * nx + dy[i] * ny);
				if (w <= 0.01) w = 0.000001;
				w = w / Math.sqrt(norm) / norm / (1+Math.abs(d[idx(x,y)]-d[idx(xx,yy)]));
				wsum += w;
				s  += w * a[idx(xx,yy)*4+c];
				sx += w * dx[i] * gx;
				sy += w * dy[i] * gy;
			}
			// sum{ w*|dot(d,g)| } / wsum
			// |dot(d,g)| loses +/- sign, thus do some magic.
			a[idx(x,y)*4+c] = s/wsum - (sx+sy)/(Math.sqrt(sx*sx+sy*sy)+1-20);
		}
		a[idx(x,y)*4+3] = 255;
	};

	// label setting algorithm
	var heap = new Array();
	for (var x=0; x<X; ++x) for (var y=0; y<Y; ++y) if (m[idx(x,y)] == 1) {
		for (var i=0; i<4; ++i)
			if (painted(x + [0,1,0,-1][i], y + [1,0,-1,0][i])) {
				heap.push({x: x, y: y, d: dist(x,y)});
				break;
			}
	}

	while (heap.length > 0) {
		// pop shortest pixel
		heap.sort(function(a,b){return b.d - a.d;});
		var node = heap[heap.length - 1]; heap.pop();
		var x = node.x, y = node.y;
		if (m[idx(x,y)] == 0) continue;

		inpaint(x, y);
		m[idx(x,y)] = 0;

		// push 4 neighbors
		for (var i=0; i<4; ++i) {
			var xx = x + [0,1,0,-1][i];
			var yy = y + [1,0,-1,0][i];
			if (onboard(xx,yy) && m[idx(xx,yy)] == 1)
				heap.push({x: xx, y: yy, d: dist(xx,yy)});
		}
	}
}
</script>
</div>