/*! jscanify v1.0.0 | (c) ColonelParrot and other contributors | MIT License */ (function (global, factory) { typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.jscanify = factory()); })(this, function () { "use strict"; /** * Calculates distance between two points. Each point must have `x` and `y` property * @param {*} p1 point 1 * @param {*} p2 point 2 * @returns distance between two points */ function distance(p1, p2) { return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2); } class jscanify { constructor() { } /** * Finds the contour of the paper within the image * @param {*} img image to process * @returns the biggest contour inside the image */ findPaperContour(img) { const imgGray = new cv.Mat(); cv.cvtColor(img, imgGray, cv.COLOR_RGBA2GRAY); const imgBlur = new cv.Mat(); cv.GaussianBlur( imgGray, imgBlur, new cv.Size(5, 5), 0, 0, cv.BORDER_DEFAULT ); const imgThresh = new cv.Mat(); cv.threshold( imgBlur, imgThresh, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU ); let contours = new cv.MatVector(); let hierarchy = new cv.Mat(); cv.findContours( imgThresh, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE ); let maxArea = 0; let maxContourIndex = -1; for (let i = 0; i < contours.size(); ++i) { let contourArea = cv.contourArea(contours.get(i)); if (contourArea > maxArea) { maxArea = contourArea; maxContourIndex = i; } } const maxContour = contours.get(maxContourIndex); imgGray.delete(); imgBlur.delete(); imgThresh.delete(); contours.delete(); hierarchy.delete(); return maxContour; } /** * Highlights the paper detected inside the image. * @param {*} image image to process * @param {*} options options for highlighting. Accepts `color` and `thickness` parameter * @returns `HTMLCanvasElement` with original image and paper highlighted */ highlightPaper(image, options) { options = options || {}; options.color = options.color || "orange"; options.thickness = options.thickness || 10; const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); const img = cv.imread(image); const maxContour = this.findPaperContour(img); if (maxContour) { const { topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner, } = this.getCornerPoints(maxContour, img); cv.imshow(canvas, img); if ( topLeftCorner && topRightCorner && bottomLeftCorner && bottomRightCorner ) { ctx.strokeStyle = options.color; ctx.lineWidth = options.thickness; ctx.beginPath(); ctx.moveTo(...Object.values(topLeftCorner)); ctx.lineTo(...Object.values(topRightCorner)); ctx.lineTo(...Object.values(bottomRightCorner)); ctx.lineTo(...Object.values(bottomLeftCorner)); ctx.lineTo(...Object.values(topLeftCorner)); ctx.stroke(); } } img.delete(); return canvas; } /** * Extracts and undistorts the image detected within the frame. * @param {*} image image to process * @param {*} resultWidth desired result paper width * @param {*} resultHeight desired result paper height * @param {*} onComplete callback with `HTMLCanvasElement` passed - the unwarped paper * @param {*} cornerPoints optional custom corner points, in case automatic corner points are incorrect */ extractPaper(image, resultWidth, resultHeight, onComplete, cornerPoints) { const canvas = document.createElement("canvas"); const img = cv.imread(image); const maxContour = this.findPaperContour(img); const { topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner, } = cornerPoints || this.getCornerPoints(maxContour, img); let warpedDst = new cv.Mat(); let dsize = new cv.Size(resultWidth, resultHeight); let srcTri = cv.matFromArray(4, 1, cv.CV_32FC2, [ topLeftCorner.x, topLeftCorner.y, topRightCorner.x, topRightCorner.y, bottomLeftCorner.x, bottomLeftCorner.y, bottomRightCorner.x, bottomRightCorner.y, ]); let dstTri = cv.matFromArray(4, 1, cv.CV_32FC2, [ 0, 0, resultWidth, 0, 0, resultHeight, resultWidth, resultHeight, ]); let M = cv.getPerspectiveTransform(srcTri, dstTri); cv.warpPerspective( img, warpedDst, M, dsize, cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar() ); cv.imshow(canvas, warpedDst); const newImg = document.createElement("img"); newImg.src = canvas.toDataURL(); newImg.onload = function () { // flip unwarped image let ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, canvas.width, canvas.height); canvas.width = resultWidth; canvas.height = resultHeight; ctx.setTransform(1, 0, 0, -1, 0, canvas.height); ctx.drawImage(newImg, 0, 0); ctx.setTransform(1, 0, 0, 1, 0, 0); img.delete(); warpedDst.delete(); onComplete(canvas); }; } /** * Calculates the corner points of a contour. * @param {*} contour contour from {@link findPaperContour} * @returns object with properties `topLeftCorner`, `topRightCorner`, `bottomLeftCorner`, `bottomRightCorner`, each with `x` and `y` property */ getCornerPoints(contour) { let rect = cv.minAreaRect(contour); const center = rect.center; let topLeftCorner; let topLeftCornerDist = 0; let topRightCorner; let topRightCornerDist = 0; let bottomLeftCorner; let bottomLeftCornerDist = 0; let bottomRightCorner; let bottomRightCornerDist = 0; for (let i = 0; i < contour.data32S.length; i += 2) { const point = { x: contour.data32S[i], y: contour.data32S[i + 1] }; const dist = distance(point, center); if (point.x < center.x && point.y > center.y) { // top left if (dist > topLeftCornerDist) { topLeftCorner = point; topLeftCornerDist = dist; } } else if (point.x > center.x && point.y > center.y) { // top right if (dist > topRightCornerDist) { topRightCorner = point; topRightCornerDist = dist; } } else if (point.x < center.x && point.y < center.y) { // bottom left if (dist > bottomLeftCornerDist) { bottomLeftCorner = point; bottomLeftCornerDist = dist; } } else if (point.x > center.x && point.y < center.y) { // bottom right if (dist > bottomRightCornerDist) { bottomRightCorner = point; bottomRightCornerDist = dist; } } } return { topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner, }; } } if (typeof module !== "undefined") { module.exports = { jscanify }; } return jscanify; });