ok
This commit is contained in:
15
package-lock.json
generated
15
package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"js-levenshtein": "^1.1.6",
|
||||||
"pixelmatch": "^7.1.0",
|
"pixelmatch": "^7.1.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
@@ -10234,6 +10235,14 @@
|
|||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/js-levenshtein": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@@ -14101,6 +14110,12 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-blocking": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/set-cookie-parser": {
|
"node_modules/set-cookie-parser": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"js-levenshtein": "^1.1.6",
|
||||||
"pixelmatch": "^7.1.0",
|
"pixelmatch": "^7.1.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<title>React App</title>
|
<title>React App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import Modal from "./Modal";
|
import Modal from "./Modal";
|
||||||
import PaginatedFormEditable from "./PaginatedFormEditable";
|
import PaginatedFormEditable from "./PaginatedFormEditable";
|
||||||
import pixelmatch from "pixelmatch";
|
|
||||||
import Tesseract from "tesseract.js";
|
|
||||||
|
|
||||||
const STORAGE_KEY = "camera_canvas_gallery";
|
const STORAGE_KEY = "camera_canvas_gallery";
|
||||||
|
|
||||||
@@ -41,35 +39,25 @@ const CameraCanvas = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isImageSimilar = (canvasA, canvasB) => {
|
// const isImageSimilar = (canvasA, canvasB) => {
|
||||||
const ctxA = canvasA.getContext("2d");
|
// const ctxA = canvasA.getContext("2d");
|
||||||
const ctxB = canvasB.getContext("2d");
|
// const ctxB = canvasB.getContext("2d");
|
||||||
|
|
||||||
const imgA = ctxA.getImageData(0, 0, canvasA.width, canvasA.height);
|
// const imgA = ctxA.getImageData(0, 0, canvasA.width, canvasA.height);
|
||||||
const imgB = ctxB.getImageData(0, 0, canvasB.width, canvasB.height);
|
// const imgB = ctxB.getImageData(0, 0, canvasB.width, canvasB.height);
|
||||||
|
|
||||||
const diffPixels = pixelmatch(
|
// const diffPixels = pixelmatch(
|
||||||
imgA.data,
|
// imgA.data,
|
||||||
imgB.data,
|
// imgB.data,
|
||||||
null,
|
// null,
|
||||||
canvasA.width,
|
// canvasA.width,
|
||||||
canvasA.height,
|
// canvasA.height,
|
||||||
{ threshold: 0.5 }
|
// { threshold: 0.5 }
|
||||||
);
|
// );
|
||||||
|
|
||||||
const similarity = diffPixels / (canvasA.width * canvasA.height);
|
// const similarity = diffPixels / (canvasA.width * canvasA.height);
|
||||||
return similarity < 0.2; // you can adjust the threshold
|
// return similarity < 0.2; // you can adjust the threshold
|
||||||
};
|
// };
|
||||||
|
|
||||||
const extractTextFromCanvas = async (canvas) => {
|
|
||||||
const dataUrl = canvas.toDataURL("image/png");
|
|
||||||
|
|
||||||
const result = await Tesseract.recognize(dataUrl, "ind", {
|
|
||||||
logger: (m) => console.log(m), // opsional: untuk melihat progress
|
|
||||||
});
|
|
||||||
|
|
||||||
return result.data.text;
|
|
||||||
};
|
|
||||||
|
|
||||||
const rectRef = useRef({
|
const rectRef = useRef({
|
||||||
x: 0,
|
x: 0,
|
||||||
@@ -235,49 +223,43 @@ const CameraCanvas = () => {
|
|||||||
const imageDataUrl = cropCanvas.toDataURL("image/png", 1.0);
|
const imageDataUrl = cropCanvas.toDataURL("image/png", 1.0);
|
||||||
setCapturedImage(imageDataUrl);
|
setCapturedImage(imageDataUrl);
|
||||||
|
|
||||||
// 👉 Load sample KTP and compare
|
|
||||||
const sampleKtpCanvas = await loadImageToCanvas(
|
|
||||||
"/ktp.jpeg",
|
|
||||||
cropCanvas.width,
|
|
||||||
cropCanvas.height
|
|
||||||
);
|
|
||||||
|
|
||||||
const isSimilar = isImageSimilar(cropCanvas, sampleKtpCanvas);
|
|
||||||
if (isSimilar) {
|
|
||||||
const extractedText = await extractTextFromCanvas(cropCanvas);
|
|
||||||
console.log("OCR Result:", extractedText);
|
|
||||||
|
|
||||||
const lowercaseText = extractedText.toLowerCase();
|
|
||||||
|
|
||||||
if (
|
|
||||||
lowercaseText.includes("provinsi") ||
|
|
||||||
lowercaseText.includes("nik") ||
|
|
||||||
lowercaseText.includes("kewarganegaraan")
|
|
||||||
) {
|
|
||||||
setKTPdetected(true);
|
setKTPdetected(true);
|
||||||
} else {
|
|
||||||
setKTPdetected(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
// Continue to OCR etc...
|
// Continue to OCR etc...
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function base64ToFile(base64Data, fileName) {
|
||||||
|
const arr = base64Data.split(",");
|
||||||
|
const mime = arr[0].match(/:(.*?);/)[1];
|
||||||
|
const bstr = atob(arr[1]);
|
||||||
|
let n = bstr.length;
|
||||||
|
const u8arr = new Uint8Array(n);
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = bstr.charCodeAt(n);
|
||||||
|
}
|
||||||
|
return new File([u8arr], fileName, { type: mime });
|
||||||
|
}
|
||||||
|
|
||||||
const ReadImage = async (capturedImage) => {
|
const ReadImage = async (capturedImage) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
let res = await fetch(
|
// Ubah base64 ke file
|
||||||
|
const file = base64ToFile(capturedImage, "image.jpg");
|
||||||
|
|
||||||
|
// Gunakan FormData
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("image", file);
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
"https://bot.kediritechnopark.com/webhook/mastersnapper/read",
|
"https://bot.kediritechnopark.com/webhook/mastersnapper/read",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ image: capturedImage }),
|
body: formData,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -480,18 +462,7 @@ const CameraCanvas = () => {
|
|||||||
rectHeight
|
rectHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
const ktpSample = new Image();
|
setKTPdetected(true);
|
||||||
ktpSample.src = "/ktp.jpeg";
|
|
||||||
ktpSample.onload = () => {
|
|
||||||
const sampleCanvas = document.createElement("canvas");
|
|
||||||
sampleCanvas.width = rectWidth;
|
|
||||||
sampleCanvas.height = rectHeight;
|
|
||||||
const sampleCtx = sampleCanvas.getContext("2d");
|
|
||||||
sampleCtx.drawImage(ktpSample, 0, 0, rectWidth, rectHeight);
|
|
||||||
|
|
||||||
const isSimilar = isImageSimilar(cropCanvas, sampleCanvas);
|
|
||||||
setKTPdetected(isSimilar);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
image.src = imageDataUrl;
|
image.src = imageDataUrl;
|
||||||
};
|
};
|
||||||
@@ -539,8 +510,9 @@ const CameraCanvas = () => {
|
|||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
top: "-17px",
|
bottom: 0,
|
||||||
position: "relative",
|
width: "100%",
|
||||||
|
position: "absolute",
|
||||||
padding: "20px",
|
padding: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -588,9 +560,7 @@ const CameraCanvas = () => {
|
|||||||
capturedImage &&
|
capturedImage &&
|
||||||
!fileTemp && (
|
!fileTemp && (
|
||||||
<div>
|
<div>
|
||||||
<h3 style={{ marginTop: 0 }}>
|
<h4 style={{ marginTop: 0 }}>Tinjau Gambar</h4>
|
||||||
KTP {!KTPdetected && "Tidak"} Terdeteksi
|
|
||||||
</h3>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: 10,
|
padding: 10,
|
||||||
|
|||||||
Reference in New Issue
Block a user