diff --git a/frontend/components/DebugRotationPanel.tsx b/frontend/components/DebugRotationPanel.tsx index 2c4c44b6..e1a44b8f 100644 --- a/frontend/components/DebugRotationPanel.tsx +++ b/frontend/components/DebugRotationPanel.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react'; import { Item } from '@/lib/db'; +import { X } from 'lucide-react'; interface DebugRotationPanelProps { item: Item; @@ -9,6 +10,7 @@ interface DebugRotationPanelProps { originalCropBounds?: { x: number; y: number; width: number; height: number }; originalRotation?: number; imageProcessing?: any; + onClose: () => void; } export function DebugRotationPanel({ @@ -17,13 +19,14 @@ export function DebugRotationPanel({ originalCropBounds, originalRotation = 0, imageProcessing, + onClose, }: DebugRotationPanelProps) { const [rotation, setRotation] = useState(originalRotation); - const [cropBounds, setCropBounds] = useState(originalCropBounds); const canvasRef = useRef(null); const [logs, setLogs] = useState([]); + const imageRef = useRef(null); const [imageLoaded, setImageLoaded] = useState(false); - const imageRef = useRef(null); + const [scaledDimensions, setScaledDimensions] = useState({ width: 0, height: 0, scale: 1 }); const commonAngles = [ { label: '0°', value: 0 }, @@ -36,138 +39,140 @@ export function DebugRotationPanel({ { label: '±180°', value: 180 }, ]; + const addLog = (message: string) => { + setLogs((prev) => [...prev, `[${new Date().toLocaleTimeString()}] ${message}`]); + }; + useEffect(() => { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => { imageRef.current = img; setImageLoaded(true); - addLog(`Image loaded: ${img.width}Ɨ${img.height}px`); + addLog(`šŸ“ø Image loaded: ${img.width}Ɨ${img.height}px`); + + // Calculate scaled dimensions to fit canvas + const maxWidth = 600; + const maxHeight = 500; + const scale = Math.min(maxWidth / img.width, maxHeight / img.height); + const scaledW = img.width * scale; + const scaledH = img.height * scale; + setScaledDimensions({ width: scaledW, height: scaledH, scale }); + addLog(`šŸ“ Scaled to fit: ${scaledW.toFixed(0)}Ɨ${scaledH.toFixed(0)}px (scale ${scale.toFixed(2)}x)`); }; img.onerror = () => addLog('āŒ Failed to load image'); img.src = imageUrl; }, [imageUrl]); - const addLog = (message: string) => { - setLogs((prev) => [...prev, `[${new Date().toLocaleTimeString()}] ${message}`]); - }; - useEffect(() => { - if (!imageLoaded || !imageRef.current || !canvasRef.current) return; + if (!imageLoaded || !imageRef.current || !canvasRef.current || !originalCropBounds) return; - const img = imageRef.current; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); if (!ctx) return; - addLog(`\n--- Processing Step ---`); - addLog(`Original image: ${img.width}Ɨ${img.height}px`); + const img = imageRef.current; + const { scale } = scaledDimensions; - if (cropBounds) { - addLog(`Crop bounds: x=${cropBounds.x}, y=${cropBounds.y}, w=${cropBounds.width}, h=${cropBounds.height}`); - addLog(`Crop rect: (${cropBounds.x}, ${cropBounds.y}, ${cropBounds.x + cropBounds.width}, ${cropBounds.y + cropBounds.height})`); - } + // Set canvas size to match scaled image + canvas.width = scaledDimensions.width; + canvas.height = scaledDimensions.height; - // Set canvas size to match cropped result (or full image if no crop) - const finalWidth = cropBounds ? cropBounds.width : img.width; - const finalHeight = cropBounds ? cropBounds.height : img.height; + addLog(`\nšŸŽØ --- Drawing Preview ---`); + addLog(`Rotation: ${rotation}°`); - // Account for rotation expanding the canvas - let displayWidth = finalWidth; - let displayHeight = finalHeight; + // Draw scaled image + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); - const radians = (rotation * Math.PI) / 180; - const cos = Math.abs(Math.cos(radians)); - const sin = Math.abs(Math.sin(radians)); + // Draw crop bounding box on the ORIGINAL image + const cropX = originalCropBounds.x * scale; + const cropY = originalCropBounds.y * scale; + const cropW = originalCropBounds.width * scale; + const cropH = originalCropBounds.height * scale; - if (Math.abs(rotation % 90) !== 0) { - displayWidth = Math.ceil(finalWidth * cos + finalHeight * sin); - displayHeight = Math.ceil(finalWidth * sin + finalHeight * cos); - } - - canvas.width = displayWidth; - canvas.height = displayHeight; - - addLog(`After crop: ${finalWidth}Ɨ${finalHeight}px`); - addLog(`Applying rotation: ${rotation}°`); - addLog(`Canvas with expansion: ${displayWidth}Ɨ${displayHeight}px`); - - // Draw: Crop → Rotate - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - // Save context state + // Save state ctx.save(); - // Move to center, rotate, move back - ctx.translate(displayWidth / 2, displayHeight / 2); - ctx.rotate((rotation * Math.PI) / 180); - ctx.translate(-finalWidth / 2, -finalHeight / 2); + // Draw semi-transparent overlay (darken everything outside crop) + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); - // Draw cropped portion of image - if (cropBounds) { - ctx.drawImage( - img, - cropBounds.x, - cropBounds.y, - cropBounds.width, - cropBounds.height, - 0, - 0, - cropBounds.width, - cropBounds.height - ); - } else { - ctx.drawImage(img, 0, 0); + // Clear the crop area (show full brightness) + ctx.clearRect(cropX, cropY, cropW, cropH); + + // Draw crop bounds rectangle + ctx.strokeStyle = '#00FF00'; + ctx.lineWidth = 3; + ctx.strokeRect(cropX, cropY, cropW, cropH); + + // Draw rotation indicator (rotated rectangle inside crop area) + if (Math.abs(rotation) > 0.5) { + ctx.strokeStyle = '#FFB800'; + ctx.lineWidth = 2; + ctx.setLineDash([5, 5]); + + // Draw a rotated rectangle showing where text will be after rotation + ctx.save(); + ctx.translate(cropX + cropW / 2, cropY + cropH / 2); + ctx.rotate((rotation * Math.PI) / 180); + ctx.strokeRect(-cropW / 2, -cropH / 2, cropW, cropH); + ctx.restore(); } ctx.restore(); - addLog(`āœ… Preview rendered`); - addLog(`Final image: ${displayWidth}Ɨ${displayHeight}px`); - }, [imageLoaded, rotation, cropBounds]); + // Logs + addLog(`Crop area: (${originalCropBounds.x}, ${originalCropBounds.y}) ${originalCropBounds.width}Ɨ${originalCropBounds.height}px`); + addLog(`Scaled crop: (${cropX.toFixed(0)}, ${cropY.toFixed(0)}) ${cropW.toFixed(0)}Ɨ${cropH.toFixed(0)}px`); + if (Math.abs(rotation) > 0.5) { + addLog(`āœ… Rotation overlay shown in ORANGE`); + } + }, [imageLoaded, rotation, originalCropBounds, scaledDimensions]); return ( -
-
-
-

šŸ”§ Debug Rotation Panel

-

Item: {item.name}

+
+
+ {/* Header */} +
+

šŸ”§ Debug Rotation & Crop

+
-
+
{/* Left: Controls */} -
+
{/* Rotation Slider */}
-
{/* Quick Presets */}
- -
+ +
{commonAngles.map((angle) => (
{/* Original Values */} - {(originalRotation !== undefined || originalCropBounds) && ( -
-

Original Values

-
+ {originalRotation !== undefined && ( +
+

Original Values

+
Rotation: {originalRotation}°
{originalCropBounds && ( -
- Crop: ({originalCropBounds.x}, {originalCropBounds.y}) {originalCropBounds.width}Ɨ{originalCropBounds.height} -
+ <> +
Crop X: {originalCropBounds.x}
+
Crop Y: {originalCropBounds.y}
+
Width: {originalCropBounds.width}
+
Height: {originalCropBounds.height}
+ )} {imageProcessing?.confidence && (
Confidence: {(imageProcessing.confidence * 100).toFixed(0)}%
@@ -193,34 +201,37 @@ export function DebugRotationPanel({
)} - - {/* Save Button */} -
- {/* Right: Preview + Logs */} -
- {/* Canvas Preview */} -
-

Processed Preview

+ {/* Center: Canvas Preview */} +
+

Original Image with Crop Box

+
- - {/* Logs */} -
-

Debug Logs

-
- {logs.map((log, i) => ( -
{log}
- ))} -
+
+
🟢 Green = Crop area
+
🟠 Orange = Rotation effect
+ + {/* Right: Logs */} +
+

Debug Logs

+
+ {logs.map((log, i) => ( +
{log}
+ ))} +
+
+
+ + {/* Footer */} +
+ Find the rotation angle that makes the label text readable, then report the value back.
diff --git a/frontend/components/ItemDetailModal.tsx b/frontend/components/ItemDetailModal.tsx index 8ee821c7..22a32c2c 100644 --- a/frontend/components/ItemDetailModal.tsx +++ b/frontend/components/ItemDetailModal.tsx @@ -201,6 +201,7 @@ export default function ItemDetailModal({ originalCropBounds={item.labels_data ? JSON.parse(item.labels_data).image_processing?.crop_bounds : undefined} originalRotation={item.labels_data ? JSON.parse(item.labels_data).image_processing?.rotation_degrees : undefined} imageProcessing={item.labels_data ? JSON.parse(item.labels_data).image_processing : undefined} + onClose={() => setShowDebugPanel(false)} /> )}