This commit is contained in:
Vassshhh
2025-09-29 09:28:18 +07:00
parent f30087d4f2
commit 57c3debfab

View File

@@ -20,6 +20,7 @@ const SPINNER_ANIMATION_STYLE = `
}
`;
const cameraModalStyles = {
modalOverlay: {
position: "fixed", top: 0, left: 0, right: 0, bottom: 0,
@@ -108,7 +109,7 @@ const cameraModalStyles = {
}
};
const useCamera = ({ videoRef, canvasRef }) => {
const useCamera = ({ videoRef, canvasRef, mode }) => {
const streamRef = useRef(null);
const guideRectRef = useRef({ x: 0, y: 0, width: 0, height: 0, radius: 20 });
const animationFrameId = useRef(null);
@@ -218,15 +219,25 @@ const useCamera = ({ videoRef, canvasRef }) => {
ctx.scale(pixelRatio, pixelRatio);
// Calculate guide rectangle (portrait ratio for ID cards)
const portraitRatio = 0.63; // Width/Height ratio for ID cards
let rectWidth = viewportWidth * 0.85;
let rectHeight = rectWidth / portraitRatio;
// hitung ratio sesuai mode
const portraitRatio = 0.63; // KTP (lebih tinggi)
const landscapeRatio = 1.58; // selalu horizontal
// Ensure it fits in viewport
if (rectHeight > viewportHeight * 0.7) {
let rectWidth, rectHeight;
if (mode === "portrait") {
rectWidth = viewportWidth * 0.85;
rectHeight = rectWidth / portraitRatio;
} else {
rectWidth = viewportWidth * 0.85;
rectHeight = rectWidth / landscapeRatio;
}
// pastikan tetap muat di layar
if (rectHeight > viewportHeight * 0.7) {
rectHeight = viewportHeight * 0.7;
rectWidth = rectHeight * portraitRatio;
}
rectWidth = rectHeight * (mode === "portrait" ? portraitRatio : landscapeRatio);
}
guideRectRef.current = {
x: (viewportWidth - rectWidth) / 2,
@@ -249,7 +260,7 @@ const useCamera = ({ videoRef, canvasRef }) => {
};
drawLoop();
}, [videoRef, canvasRef]);
}, [videoRef, canvasRef, mode]);
const startCamera = useCallback(async () => {
console.log("Starting camera...");
@@ -322,7 +333,7 @@ const useCamera = ({ videoRef, canvasRef }) => {
}
}, [videoRef, stopCameraStream, setupCanvasAndDrawLoop]);
const captureImage = useCallback(() => {
const captureImage = useCallback(() => {
const video = videoRef.current;
const canvas = canvasRef.current;
@@ -341,11 +352,6 @@ const useCamera = ({ videoRef, canvasRef }) => {
const videoWidth = video.videoWidth;
const videoHeight = video.videoHeight;
console.log("Video dimensions:", videoWidth, "x", videoHeight);
console.log("Canvas dimensions:", canvasRect.width, "x", canvasRect.height);
console.log("Guide rect:", guide);
// Calculate how the video is displayed (object-fit: cover)
const videoAspectRatio = videoWidth / videoHeight;
const canvasAspectRatio = canvasRect.width / canvasRect.height;
@@ -353,49 +359,54 @@ const useCamera = ({ videoRef, canvasRef }) => {
let scaleX, scaleY;
if (videoAspectRatio > canvasAspectRatio) {
// Video is wider - it will be cropped horizontally
displayHeight = canvasRect.height;
displayWidth = displayHeight * videoAspectRatio;
offsetX = (canvasRect.width - displayWidth) / 2;
offsetY = 0;
} else {
// Video is taller - it will be cropped vertically
displayWidth = canvasRect.width;
displayHeight = displayWidth / videoAspectRatio;
offsetX = 0;
offsetY = (canvasRect.height - displayHeight) / 2;
}
// Calculate scale factors from displayed video to actual video
scaleX = videoWidth / displayWidth;
scaleY = videoHeight / displayHeight;
// Calculate crop coordinates in video space
const cropX = Math.max(0, (guide.x - offsetX) * scaleX);
const cropY = Math.max(0, (guide.y - offsetY) * scaleY);
const cropWidth = Math.min(videoWidth - cropX, guide.width * scaleX);
const cropHeight = Math.min(videoHeight - cropY, guide.height * scaleY);
console.log("Crop coordinates:", { cropX, cropY, cropWidth, cropHeight });
console.log("Scale factors:", { scaleX, scaleY });
console.log("Display info:", { displayWidth, displayHeight, offsetX, offsetY });
// Create crop canvas
// --- Crop canvas ---
const cropCanvas = document.createElement("canvas");
cropCanvas.width = Math.round(cropWidth);
cropCanvas.height = Math.round(cropHeight);
const cropCtx = cropCanvas.getContext("2d");
// Draw cropped portion
cropCtx.drawImage(
video,
Math.round(cropX), Math.round(cropY), Math.round(cropWidth), Math.round(cropHeight),
0, 0, Math.round(cropWidth), Math.round(cropHeight)
);
console.log("Image cropped successfully");
// --- Jika mode portrait, rotasi ke landscape ---
if (mode === "portrait") {
const rotatedCanvas = document.createElement("canvas");
rotatedCanvas.width = cropCanvas.height; // dibalik
rotatedCanvas.height = cropCanvas.width;
const rctx = rotatedCanvas.getContext("2d");
// Putar 90° CW
rctx.translate(rotatedCanvas.width / 2, rotatedCanvas.height / 2);
rctx.rotate(-90 * Math.PI / 180);
rctx.drawImage(cropCanvas, -cropCanvas.width / 2, -cropCanvas.height / 2);
return rotatedCanvas.toDataURL("image/jpeg", 0.9);
}
return cropCanvas.toDataURL("image/jpeg", 0.9);
}, [videoRef, canvasRef]);
}, [videoRef, canvasRef, mode]);
useEffect(() => {
return () => {
@@ -414,8 +425,13 @@ const CameraModal = ({ isOpen, onClose, onCapture }) => {
const [cameraReady, setCameraReady] = useState(false);
const videoRef = useRef(null);
const canvasRef = useRef(null);
const [mode, setMode] = useState("portrait");
const toggleMode = () => {
setMode((prev) => (prev === "portrait" ? "landscape" : "portrait"));
};
const { startCamera, stopCameraStream, captureImage } = useCamera({ videoRef, canvasRef, mode });
const { startCamera, stopCameraStream, captureImage } = useCamera({ videoRef, canvasRef });
// Handle modal open/close and step changes
useEffect(() => {
@@ -514,6 +530,25 @@ const CameraModal = ({ isOpen, onClose, onCapture }) => {
<div style={cameraModalStyles.modalContent}>
{step === "camera" && (
<div style={cameraModalStyles.cameraContainer}>
<button
onClick={toggleMode}
style={{
position: "absolute",
top: "20px",
right: "20px",
backgroundColor: "rgba(0,0,0,0.6)",
color: "white",
border: "none",
borderRadius: "8px",
padding: "8px 12px",
fontSize: "14px",
cursor: "pointer",
zIndex: 11,
}}
>
{mode === "portrait" ? "Mode Horizontal" : "Mode Vertikal"}
</button>
<video
ref={videoRef}
autoPlay
@@ -521,6 +556,7 @@ const CameraModal = ({ isOpen, onClose, onCapture }) => {
muted
style={cameraModalStyles.video}
/>
<canvas
ref={canvasRef}
style={cameraModalStyles.canvas}
@@ -534,9 +570,7 @@ const CameraModal = ({ isOpen, onClose, onCapture }) => {
&times;
</button>
<div style={cameraModalStyles.instructionText}>
Posisikan dokumen dalam area hijau dan tekan tombol untuk mengambil gambar
</div>
<div style={cameraModalStyles.cameraControls}>
<button