var topLeftY = 0;
var topRightX = 0;
var topRightY = 0;
var bottomLeftX = 0;
var bottomLeftY = 0;
var bottomRightX = 0;
var bottomRightY = 0;
var offset = 200;
var dragging = null;
var fileName = null;
var whiteThreshold = 100;
var worker = null;
function getMousePos(canvas, evt) {
    var rect = canvas.getBoundingClientRect();
    return {
        x: (evt.clientX - rect.left) / (rect.right - rect.left) * canvas.width,
        y: (evt.clientY - rect.top) / (rect.bottom - rect.top) * canvas.height
    };
}
function render() {
	var src = document.getElementById("sourcecanvas");
	var dst = document.getElementById("destcanvas");
	whiteThreshold = document.getElementById("whitethreshold").value;
	var finalWidth = Math.floor(((topRightX + bottomRightX) / 2) - ((topLeftX + bottomLeftX) / 2));
	var finalHeight = Math.floor(((bottomLeftY + bottomRightY) / 2) - ((topLeftY + topRightY) / 2));
	dst.width = finalWidth;
	dst.height = finalHeight;
	var srcd = src.getContext("2d").getImageData(0, 0, src.width, src.height);
	var dstd = dst.getContext("2d").getImageData(0, 0, dst.width, dst.height);
	var sw = src.width;
	var dw = dst.width;
	var dh = dst.height;
	//processImage(srcd, dstd, sw, dw, dh, topLeftX, topLeftY, bottomLeftX, bottomLeftY, topRightX, topRightY, bottomRightX, bottomRightY);
	worker.postMessage({
		srcd: srcd,
		dstd: dstd,
		sw: sw,
		dw: dw,
		dh: dh,
		whiteThreshold: whiteThreshold,
		topLeftX: topLeftX,
		topLeftY: topLeftY,
		bottomLeftX: bottomLeftX,
		bottomLeftY: bottomLeftY,
		topRightX: topRightX,
		topRightY: topRightY,
		bottomRightX: bottomRightX,
		bottomRightY: bottomRightY
	});
	//var ctx = dst.getContext("2d");
	//ctx.putImageData(dstd, 0, 0);
}
function finishRender(e) {
	var dst = document.getElementById("destcanvas");
	var ctx = dst.getContext("2d");
	ctx.putImageData(e.data.dstd, 0, 0);
	prepareDownloadImage();
}
function processImage(srcd, dstd, sw, dw, dh, whiteThreshold, topLeftX, topLeftY, bottomLeftX, bottomLeftY, topRightX, topRightY, bottomRightX, bottomRightY) {
	for (var y = 0; y < dh; y++) {
		var dy = y / dh;
		var startX = topLeftX + dy*(bottomLeftX-topLeftX);
		var startY = topLeftY + dy*(bottomLeftY-topLeftY);
		var endX = topRightX + dy*(bottomRightX-topRightX);
		var endY = topRightY + dy*(bottomRightY-topRightY);
		for (var x = 0; x < dw; x++) {
			var dx = x / dw;
			var pointX = Math.floor(startX + dx*(endX-startX));
			var pointY = Math.floor(startY + dx*(endY-startY));
			var dstxy = (y*dw+x)*4;
			var srcxy = (pointY*sw+pointX)*4;
			if (((srcd.data[srcxy+0] + srcd.data[srcxy+1] + srcd.data[srcxy+2]) / 3) > whiteThreshold) {
				dstd.data[dstxy+0] = 255;
				dstd.data[dstxy+1] = 255;
				dstd.data[dstxy+2] = 255;
				dstd.data[dstxy+3] = 255;
			} else {
				dstd.data[dstxy+0] = srcd.data[srcxy+0];
				dstd.data[dstxy+1] = srcd.data[srcxy+1];
				dstd.data[dstxy+2] = srcd.data[srcxy+2];
				dstd.data[dstxy+3] = srcd.data[srcxy+3];
			}
		}
	}
}
function updatePoints() {
	var overlay = document.getElementById("overlay");
	var ctx = overlay.getContext("2d");
	var s = 10;
	overlay.width = overlay.width;
	ctx.clearRect(0, 0, ctx.width, ctx.height);
	ctx.beginPath();
	ctx.lineWidth=s;
	ctx.moveTo(topLeftX, topLeftY);
	ctx.lineTo(topRightX,topRightY);
	ctx.lineTo(bottomRightX,bottomRightY);
	ctx.lineTo(bottomLeftX,bottomLeftY);
	ctx.lineTo(topLeftX, topLeftY);
	ctx.stroke();
	ctx.closePath();
	ctx.fillStyle="#fff";
	ctx.fillRect(topLeftX-s*3, topLeftY-s*3, s*6, s*6);
	ctx.fillRect(bottomLeftX-s*3, bottomLeftY-s*3, s*6, s*6);
	ctx.fillRect(topRightX-s*3, topRightY-s*3, s*6, s*6);
	ctx.fillRect(bottomRightX-s*3, bottomRightY-s*3, s*6, s*6);
};
function getOrientation(file, callback) {
  var reader = new FileReader();
  reader.onload = function(e) {
    var view = new DataView(e.target.result);
    if (view.getUint16(0, false) != 0xFFD8) return callback(-2);
    var length = view.byteLength, offset = 2;
    while (offset < length) {
      var marker = view.getUint16(offset, false);
      offset += 2;
      if (marker == 0xFFE1) {
        if (view.getUint32(offset += 2, false) != 0x45786966) return callback(-1);
        var little = view.getUint16(offset += 6, false) == 0x4949;
        offset += view.getUint32(offset + 4, little);
        var tags = view.getUint16(offset, little);
        offset += 2;
        for (var i = 0; i < tags; i++)
          if (view.getUint16(offset + (i * 12), little) == 0x0112)
            return callback(view.getUint16(offset + (i * 12) + 8, little));
      }
      else if ((marker & 0xFF00) != 0xFF00) break;
      else offset += view.getUint16(offset, false);
    }
    return callback(-1);
  };
  reader.readAsArrayBuffer(file);
}
function hover(e) {
	// file drag hover
	e.stopPropagation();
	e.preventDefault();
	e.target.className = (e.type == "dragover" ? "hover" : "");
}
function prepareDownloadImage() {
	var dst = document.getElementById("destcanvas");
	var downloadImage = document.getElementById("download-image");
	if (fileName) {
		downloadImage.href = dst.toDataURL("image/jpeg");
		downloadImage.download = fileName + ".jpg";
	}
}
document.addEventListener("DOMContentLoaded", function() {
	var src = document.getElementById("sourcecanvas");
	var dst = document.getElementById("destcanvas");
	var overlay = document.getElementById("overlay");
	var cc = document.getElementById("canvascontainer");
	worker = new Worker("./imageWorker.js");
        worker.onmessage = finishRender;
	document.getElementById("download-pdf").addEventListener("click", function(e) {
		var imgData = dst.toDataURL("image/jpeg", 1.0);
		var pdf = new jsPDF();
		var margin = 0;
		var width = pdf.internal.pageSize.width - margin*2;
		var height = pdf.internal.pageSize.height - margin*2;
		var pdfRatio = height / width;
		var currentRatio = dst.height / dst.width;
		console.log("page width: " + width);
		console.log("page height: " + height);
		console.log("image width: " + dst.width);
		console.log("image height: " + dst.height);
		console.log("page ratio: " + pdfRatio);
		console.log("image ratio: " + currentRatio);
		if (pdfRatio > currentRatio) {
			pdf.addImage(imgData, 'JPEG', margin, margin, width, width * currentRatio);
		} else {
			pdf.addImage(imgData, 'JPEG', margin + (width - height / currentRatio) / 2, margin, height / currentRatio, height);
		}
		pdf.save(fileName + ".pdf");
	});
	function processFiles(files) {
		if (files.length != 1) {
			alert("Please drag and drop one file");
			return;
		}
		var file = files[0];
		if (file.type.indexOf("image") != 0) {
			alert("Please drop an image");
			return;
		}
		fileName = file.name.substring(0, file.name.lastIndexOf('.'));
		var reader = new FileReader();
		reader.onload = function(e) {
			var img = new Image();
			img.onload = function(e) {
				getOrientation(file, function(orientation) {
					src.width = img.width;
					src.height = img.height;
					var ctx = src.getContext("2d");
					var width = img.width;
					var height = img.height;
					// set proper canvas dimensions before transform & export
					if ([5,6,7,8].indexOf(orientation) > -1) {
					  src.width = height;
					  src.height = width;
					} else {
					  src.width = width;
					  src.height = height;
					}
					// copy dst attributes
					dst.width = src.width;
					dst.height = src.height;
					overlay.width = src.width;
					overlay.height = src.height;
					switch (orientation) {
					  case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;
					  case 3: ctx.transform(-1, 0, 0, -1, width, height ); break;
					  case 4: ctx.transform(1, 0, 0, -1, 0, height ); break;
					  case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
					  case 6: ctx.transform(0, 1, -1, 0, height , 0); break;
					  case 7: ctx.transform(0, -1, -1, 0, height , width); break;
					  case 8: ctx.transform(0, -1, 1, 0, 0, width); break;
					  default: ctx.transform(1, 0, 0, 1, 0, 0);
					}
					ctx.drawImage(img, 0, 0);
					ctx.setTransform(1, 0, 0, 1, 0, 0);
					topLeftX = offset;
					topLeftY = offset;
					topRightX = dst.width - offset;
					topRightY = offset;
					bottomLeftX = offset;
					bottomLeftY = dst.height - offset;
					bottomRightX = dst.width - offset;
					bottomRightY = dst.height - offset;
					updatePoints();
					render();
				});
			};
			img.src = e.target.result;
		};
		reader.readAsDataURL(file);
	}
	document.getElementById("upload-file").addEventListener("change", function(e) {
		var files = e.target.files;
		processFiles(files);
	});
	overlay.addEventListener("dragover", hover, false);
	overlay.addEventListener("dragleave", hover, false);
	overlay.addEventListener("drop", function(e) {
		e.stopPropagation();
		e.preventDefault();
		e.target.classList.remove("hover");
		processFiles(e.dataTransfer.files);
	});
	overlay.addEventListener("mousedown", function(e) {
		var pos = getMousePos(src, e);
		var sen = 20;
		if ((pos.x > topLeftX - sen) && (pos.y > topLeftY - sen) && (pos.x < topLeftX + sen) && (pos.y < topLeftY + sen)) {
			dragging = "topleft";
		}
		if ((pos.x > topRightX - sen) && (pos.y > topRightY - sen) && (pos.x < topRightX + sen) && (pos.y < topRightY + sen)) {
			dragging = "topright";
		}
		if ((pos.x > bottomLeftX - sen) && (pos.y > bottomLeftY - sen) && (pos.x < bottomLeftX + sen) && (pos.y < bottomLeftY + sen)) {
			dragging = "bottomleft";
		}
		if ((pos.x > bottomRightX - sen) && (pos.y > bottomRightY - sen) && (pos.x < bottomRightX + sen) && (pos.y < bottomRightY + sen)) {
			dragging = "bottomright";
		}
		updatePoints();
	});
	overlay.addEventListener("mousemove", function(e) {
		var pos = getMousePos(src, e);
		var sen = 20;
		if ((pos.x > topLeftX - sen) && (pos.y > topLeftY - sen) && (pos.x < topLeftX + sen) && (pos.y < topLeftY + sen)) {
			overlay.style.cursor = "pointer";
		}
		else if ((pos.x > topRightX - sen) && (pos.y > topRightY - sen) && (pos.x < topRightX + sen) && (pos.y < topRightY + sen)) {
			overlay.style.cursor = "pointer";
		}
		else if ((pos.x > bottomLeftX - sen) && (pos.y > bottomLeftY - sen) && (pos.x < bottomLeftX + sen) && (pos.y < bottomLeftY + sen)) {
			overlay.style.cursor = "pointer";
		}
		else if ((pos.x > bottomRightX - sen) && (pos.y > bottomRightY - sen) && (pos.x < bottomRightX + sen) && (pos.y < bottomRightY + sen)) {
			overlay.style.cursor = "pointer";
		} else {
			overlay.style.cursor = "inherit";
		}
		if (dragging) {
			var pos = getMousePos(src, e);
			if (dragging == "topleft") {
				topLeftX = pos.x;
				topLeftY = pos.y;
			}
			if (dragging == "topright") {
				topRightX = pos.x;
				topRightY = pos.y;
			}
			if (dragging == "bottomleft") {
				bottomLeftX = pos.x;
				bottomLeftY = pos.y;
			}
			if (dragging == "bottomright") {
				bottomRightX = pos.x;
				bottomRightY = pos.y;
			}
			updatePoints();
		}
	});
	overlay.addEventListener("mouseup", function(e) {
		dragging = null;
		updatePoints();
		render();
	});
});
HTML源代码:
 | <html> | 
 | <head> | 
 | 	<title>ImageToScan.com</title> | 
 | 	<meta name="description" content="Convert images into scanned PDF documents"> | 
 | 
  | 
 | 	<meta property="og:title" content="ImageToScan.com"/> | 
 | 	<meta property="og:url" content="http://www.imagetoscan.com/"/> | 
 | 	<meta property="og:image" content="http://www.imagetoscan.com/ois-logo-fb.png"/> | 
 | 	<meta property="og:description" content="Convert images into scanned PDF documents"/> | 
 | 
  | 
 | 	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/> | 
 | 	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"/> | 
 | 	<link rel="icon" href="ois.png"/> | 
 | 
  | 
 | 	<script src="http://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.3/jspdf.min.js"></script> | 
 | 	<script src="ois.js"></script> | 
 | 	<style> | 
 | 	canvas { | 
 | 		width: 100%; | 
 | 		border: 1px solid #ccc; | 
 | 	} | 
 | 
  | 
 | 	canvas.hover { | 
 | 		background-color: #eee; | 
 | 	} | 
 | 
  | 
 | 	#canvascontainer { | 
 | 		position: relative; | 
 | 		width: 100%; | 
 | 	} | 
 | 
  | 
 | 	#overlay, #sourcecanvas { | 
 | 		position: absolute; | 
 | 		top: 0; | 
 | 		bottom: 0; | 
 | 		left: 0; | 
 | 		right: 0; | 
 | 	} | 
 | 
  | 
 | 	#overlay { | 
 | 		cursor: pointer; | 
 | 	} | 
 | 
  | 
 | 	.point { | 
 | 		position: absolute; | 
 | 		width: 10px; | 
 | 		height: 10px; | 
 | 		border-radius: 10px; | 
 | 		background-color: white; | 
 | 		boder: 1px solid #ccc; | 
 | 	} | 
 | 
  | 
 | 	#instructions { | 
 | 		text-align: center; | 
 | 		color: #aaa; | 
 | 		font-size: 30px; | 
 | 		padding-top: 50px; | 
 | 	} | 
 | 
  | 
 | 	#instructions i { | 
 | 		font-size: 100px; | 
 | 	} | 
 | 
  | 
 | 	</style> | 
 | </head> | 
 | <body> | 
 | <img src="ois-logo.png" style="width: 300px; margin: 30px auto; display: block; "> | 
 | 
  | 
 | <h1 style="background-color: #f8f8f8; border: 1px solid #ccc; padding: 20px 0; font-size: 50px; text-align: center">ImageToScan<span style="color: #888">.com</span></h1> | 
 | <div class="container"> | 
 | 
  | 
 | 	<h4 style="text-align: center">Convert images into scanned PDF documents.</h4> | 
 | 	<hr/> | 
 | 
  | 
 | 
  | 
 | 
  | 
 | 	<div class="row"> | 
 | 		<div class="col-lg-6"> | 
 | 			<h3><i class="fa fa-sign-in"></i> Source</h3> | 
 | 			<hr/> | 
 | 			<p>1. Select an image by dragging & dropping it or by using the Upload button.</p> | 
 | 			<p>2. Move the white corners to match the corners of the page in the photo.</p> | 
 | 			<p> | 
 | 			<label class="btn btn-primary btn-file"> | 
 |     			<i class="fa fa-upload"></i> Upload <input type="file" id="upload-file" style="display: none;"> | 
 | 			</label> | 
 | 
  | 
 | 			</p> | 
 | 
  | 
 | 			<div id="canvascontainer"> | 
 | 				<canvas id="sourcecanvas"></canvas> | 
 | 				<canvas id="overlay"></canvas> | 
 | 				<div id="instructions"> | 
 | 					<p><i class="fa fa-cloud-upload"></i></p> | 
 | 					<p>Drag and drop an image here</p> | 
 | 				</div> | 
 | 
  | 
 | 			</div> | 
 | 		</div> | 
 | 		<div class="col-lg-6"> | 
 | 			<h3><i class="fa fa-sign-out"></i> Destination</h3> | 
 | 			<hr/> | 
 | 			<p>3. Adjust the white threshold of the document as needed.</p> | 
 | 			<p>4. When ready, click the "Download" button, or right click the image and save.</p> | 
 | 			<div class="row"> | 
 | 
  | 
 | 				<div class="col-lg-4"> | 
 | 					<p>White Threshold: <input type="range" id="whitethreshold" min="0" max="255" value="100" onchange="render()"></p> | 
 | 				</div> | 
 | 				<div class="col-lg-8" style="text-align: right"> | 
 | 					<a id="download-image" class="btn btn-primary"><i class="fa fa-file-image-o"></i> Download Image</a> | 
 | 					<a id="download-pdf" class="btn btn-success"><i class="fa fa-file-pdf-o"></i> Download PDF</a> | 
 | 				</div> | 
 | 			</div> | 
 | 			<canvas id="destcanvas"></canvas>	 | 
 | 		</div> | 
 | 	</div> | 
 | 
  | 
 | 	<hr/> | 
 | 	<footer style="color: #666; text-align: center; margin: 30px 0; font-size: 14px">Copyright © ImageToScan.com. For support please contact <a href="mailto:support@imagetoscan.com">support@imagetoscan.com</a></footer> | 
 | </div> | 
 | 
  | 
 | </body> |