{"version":3,"sources":["jsdelivr-header.js","/npm/@sclaflin/document-scanner@0.0.1/DocumentScanner.js"],"names":[],"mappings":"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","file":"/npm/@sclaflin/document-scanner@0.0.1/DocumentScanner.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/@sclaflin/document-scanner@0.0.1/DocumentScanner.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","export const MAX_WIDTH = 480;\n\nexport default class DocumentScanner {\n\t#cv = null;\n\tconstructor(cv) {\n\t\tif(cv === null || typeof cv !== 'object')\n\t\t\tthrow new TypeError('cv must be an object.');\n\n\t\tthis.#cv = cv;\n\t}\n\tget cv() {\n\t\treturn this.#cv;\n\t}\n\tscanImage(src, addOutline = false) {\n\t\tif(!(src instanceof this.cv.Mat))\n\t\t\tthrow new TypeError('src must be an OpenCV Mat object.');\n\t\tif(typeof addOutline !== 'boolean')\n\t\t\tthrow new TypeError('addOutline must be a boolean.');\n\n\t\t// make sure the image is scaled to an appropriate size\n\t\tconst dst = new this.cv.Mat();\n\n\t\t// calculate scale scale targeting 480 pixel width\n\t\tconst scale = MAX_WIDTH / src.cols;\n\t\tthis.cv.resize(src, dst, new this.cv.Size(Math.round(src.cols * scale), Math.round(src.rows * scale)), 0, 0, this.cv.INTER_AREA);\n\n\t\t// Remove alpha channel from image\n\t\tthis.cv.cvtColor(dst, dst, this.cv.COLOR_RGBA2RGB, 0);\n\t\t\n\t\t// Convert to grayscale\n\t\tthis.cv.cvtColor(dst, dst, this.cv.COLOR_RGB2GRAY);\n\t\t\n\t\t// Apply a Gaussian blur\n\t\tthis.cv.GaussianBlur(dst, dst, new this.cv.Size(5, 5), 0);\n\t\t\n\t\t// Canny edge detection\n\t\tthis.cv.Canny(dst, dst, 75, 200);\n\t\t\n\t\t// Find contours\n\t\tconst contours = new this.cv.MatVector();\n\t\tconst hierarchy = new this.cv.Mat();\n\t\tthis.cv.findContours(dst, contours, hierarchy, this.cv.RETR_LIST, this.cv.CHAIN_APPROX_SIMPLE);\n\t\t\n\t\t// Identify the largest contour representing a page\n\t\tconst contour = this.getPageContours(\n\t\t\tthis.getLargestContours(\n\t\t\t\tthis.matVectorToArray(contours),\n\t\t\t\t1\n\t\t\t)\n\t\t)[0] || null;\n\n\t\t// reverse-scale the contour\n\t\tif(contour)\n\t\t\tthis.scaleContour(contour, 1/scale);\n\n\t\t// crop and transform the detected document\n\t\tconst img = contour ? this.fourPointTransform(src, contour) : null;\n\n\t\t// draw the contour\n\t\tif(addOutline && contour)\n\t\t\tthis.cv.drawContours(src, this.arrayToMatVector([contour]), -1, new this.cv.Scalar(0, 255, 0, 255), 3, this.cv.LINE_8, hierarchy, 0);\n\t\t\n\t\t// clean up\n\t\tcontour?.delete();\n\t\thierarchy.delete();\n\t\tcontours.delete();\n\t\tdst.delete();\n\n\t\treturn img;\n\t}\n\tscaleContour(contour, scale) {\n\t\tfor(let i = 0; i < contour.data32S.length; i++)\n\t\t\tcontour.data32S[i] *= scale;\n\t}\n\tmatVectorToArray(matVector) {\n\t\tif(!(matVector instanceof this.cv.MatVector))\n\t\t\tthrow new TypeError('matVector must be an OpenCV MatVector object.');\n\t\t\n\t\tconst out = [];\n\t\tfor(let i = 0; i < matVector.size(); i++)\n\t\t\tout.push(matVector.get(i));\n\t\treturn out;\n\t}\n\tarrayToMatVector(array) {\n\t\tconst out = new this.cv.MatVector();\n\t\tfor(const item of array)\n\t\t\tout.push_back(item);\n\n\t\treturn out;\n\t}\n\tgetLargestContours(contours, count) {\n\t\treturn [...contours]\n\t\t\t// sort by area\n\t\t\t.sort((a, b) =>\n\t\t\t\tthis.cv.contourArea(b, false) - this.cv.contourArea(a, false)\n\t\t\t)\n\t\t\t// return the requested count\n\t\t\t.slice(0, count);\n\t}\n\tgetPageContours(contours) {\n\t\tif(!Array.isArray(contours))\n\t\t\tthrow new TypeError('contours must be an Array.');\n\t\tif(contours.filter(contour => !(contour instanceof this.cv.Mat)).length > 0)\n\t\t\tthrow new TypeError('contours array must only contain OpenCV Mat objects.');\n\n\t\treturn contours\n\t\t\t.map(contour => {\n\t\t\t\t// determine the perimeter of the contour\n\t\t\t\tconst perimeter = this.cv.arcLength(contour, true);\n\n\t\t\t\t// approximate the contour with fewer vertices\n\t\t\t\tconst corners = new this.cv.Mat();\n\t\t\t\tthis.cv.approxPolyDP(contour, corners, 0.02 * perimeter, true);\n\n\t\t\t\treturn corners;\n\t\t\t})\n\t\t\t.filter(contour => {\n\t\t\t\t// does this contour have 4 points like a document would?\n\t\t\t\tconst match = contour.rows === 4;\n\n\t\t\t\t// if not, clean it up\n\t\t\t\tif(!match)\n\t\t\t\t\tcontour.delete();\n\t\t\t\t\n\t\t\t\treturn match;\n\t\t\t});\n\t}\n\tfourPointTransform(src, contour) {\n\t\tif(!(src instanceof this.cv.Mat))\n\t\t\tthrow new TypeError('src must be an OpenCV Mat object.');\n\t\tif(!(contour instanceof this.cv.Mat))\n\t\t\tthrow new TypeError('contour must be an OpenCV Mat object.');\n\t\t\n\t\t// find the corners\n\t\tconst cornerArray = [\n\t\t\tnew this.cv.Point(Math.round(contour.data32S[0]), Math.round(contour.data32S[1])),\n\t\t\tnew this.cv.Point(Math.round(contour.data32S[2]), Math.round(contour.data32S[3])),\n\t\t\tnew this.cv.Point(Math.round(contour.data32S[4]), Math.round(contour.data32S[5])),\n\t\t\tnew this.cv.Point(Math.round(contour.data32S[6]), Math.round(contour.data32S[7]))\n\t\t];\n\t\t// sort by Y position (to get top-down)\n\t\tcornerArray.sort((a, b) =>\n\t\t\t(a.y < b.y) ? -1 : (a.y > b.y) ? 1 : 0\n\t\t);\n\n\t\t// determine left/right based on x position of top and bottom 2\n\t\tconst tl = cornerArray[0].x < cornerArray[1].x ? cornerArray[0] : cornerArray[1];\n\t\tconst tr = cornerArray[0].x > cornerArray[1].x ? cornerArray[0] : cornerArray[1];\n\t\tconst bl = cornerArray[2].x < cornerArray[3].x ? cornerArray[2] : cornerArray[3];\n\t\tconst br = cornerArray[2].x > cornerArray[3].x ? cornerArray[2] : cornerArray[3];\n\n\t\t// calculate the max width/height\n\t\tconst maxWidth = Math.max(\n\t\t\tMath.hypot(br.x - bl.x, br.y - bl.y), //top width\n\t\t\tMath.hypot(tr.x - tl.x, tr.y - tl.y) //bottom width\n\t\t);\n\t\tconst maxHeight = Math.max(\n\t\t\tMath.hypot(tr.x - br.x, tr.y - br.y), //right height\n\t\t\tMath.hypot(tl.x - bl.x, tr.y - bl.y) //left height\n\t\t);\n\n\t\t// get the 3x3 perspective transformation matrix for the source coordinates and destination coordinates\n\t\tconst srcCoords = this.cv.matFromArray(4, 1, this.cv.CV_32FC2, [tl.x, tl.y, tr.x, tr.y, br.x, br.y, bl.x, bl.y]);\n\t\tconst dstCoords = this.cv.matFromArray(4, 1, this.cv.CV_32FC2, [0, 0, maxWidth - 1, 0, maxWidth - 1, maxHeight - 1, 0, maxHeight - 1]);\n\t\tconst M = this.cv.getPerspectiveTransform(srcCoords, dstCoords);\n\t\t\n\t\t// warp the source coordinates into destiniation coordinates and write to a new matrix\n\t\tlet dsize = new this.cv.Size(maxWidth, maxHeight);\n\t\tconst dst = new this.cv.Mat();\n\t\tthis.cv.warpPerspective(src, dst, M, dsize, this.cv.INTER_LINEAR, this.cv.BORDER_CONSTANT, new this.cv.Scalar());\n\n\t\t// clean up\n\t\tM.delete();\n\t\tsrcCoords.delete();\n\t\tdstCoords.delete();\n\n\t\treturn dst;\n\t}\n}"]}