This commit is contained in:
zadit biasa aja
2025-07-01 18:41:41 +00:00
parent 25ed673e70
commit a68e750276
4 changed files with 60 additions and 73 deletions

15
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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>

View File

@@ -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,