diff --git a/src/pages/CameraModal.js b/src/pages/CameraModal.js index 7ee63d9..be8a09f 100644 --- a/src/pages/CameraModal.js +++ b/src/pages/CameraModal.js @@ -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; - - // Ensure it fits in viewport - if (rectHeight > viewportHeight * 0.7) { - rectHeight = viewportHeight * 0.7; - rectWidth = rectHeight * portraitRatio; - } + // hitung ratio sesuai mode +const portraitRatio = 0.63; // KTP (lebih tinggi) +const landscapeRatio = 1.58; // selalu horizontal + +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 * (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,80 +333,80 @@ const useCamera = ({ videoRef, canvasRef }) => { } }, [videoRef, stopCameraStream, setupCanvasAndDrawLoop]); - const captureImage = useCallback(() => { - const video = videoRef.current; - const canvas = canvasRef.current; - - if (!video || video.readyState < 2) { - console.log("Video not ready for capture"); - return null; - } +const captureImage = useCallback(() => { + const video = videoRef.current; + const canvas = canvasRef.current; + + if (!video || video.readyState < 2) { + console.log("Video not ready for capture"); + return null; + } - console.log("Capturing and cropping image..."); - - const guide = guideRectRef.current; - if (!canvas) return null; + console.log("Capturing and cropping image..."); + + const guide = guideRectRef.current; + if (!canvas) return null; - // Get actual dimensions - const canvasRect = canvas.getBoundingClientRect(); - 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); + // Get actual dimensions + const canvasRect = canvas.getBoundingClientRect(); + const videoWidth = video.videoWidth; + const videoHeight = video.videoHeight; - // Calculate how the video is displayed (object-fit: cover) - const videoAspectRatio = videoWidth / videoHeight; - const canvasAspectRatio = canvasRect.width / canvasRect.height; + const videoAspectRatio = videoWidth / videoHeight; + const canvasAspectRatio = canvasRect.width / canvasRect.height; - let displayWidth, displayHeight, offsetX = 0, offsetY = 0; - let scaleX, scaleY; + let displayWidth, displayHeight, offsetX = 0, offsetY = 0; + 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; - } + if (videoAspectRatio > canvasAspectRatio) { + displayHeight = canvasRect.height; + displayWidth = displayHeight * videoAspectRatio; + offsetX = (canvasRect.width - displayWidth) / 2; + } else { + displayWidth = canvasRect.width; + displayHeight = displayWidth / videoAspectRatio; + offsetY = (canvasRect.height - displayHeight) / 2; + } - // Calculate scale factors from displayed video to actual video - scaleX = videoWidth / displayWidth; - scaleY = videoHeight / displayHeight; + 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); + 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 }); + // --- Crop canvas --- + const cropCanvas = document.createElement("canvas"); + cropCanvas.width = Math.round(cropWidth); + cropCanvas.height = Math.round(cropHeight); + const cropCtx = cropCanvas.getContext("2d"); - // Create crop canvas - const cropCanvas = document.createElement("canvas"); - cropCanvas.width = Math.round(cropWidth); - cropCanvas.height = Math.round(cropHeight); - const cropCtx = cropCanvas.getContext("2d"); + cropCtx.drawImage( + video, + Math.round(cropX), Math.round(cropY), Math.round(cropWidth), Math.round(cropHeight), + 0, 0, Math.round(cropWidth), Math.round(cropHeight) + ); + + // --- 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, mode]); - // 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"); - return cropCanvas.toDataURL("image/jpeg", 0.9); - }, [videoRef, canvasRef]); 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(() => { @@ -512,8 +528,27 @@ const CameraModal = ({ isOpen, onClose, onCapture }) => {