Spaces:
Sleeping
Sleeping
File size: 4,101 Bytes
8608e55 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
/**
* ImageCanvas.jsx
*
* This component is responsible for:
* - Displaying an uploaded image
* - Sending the image to a backend for text/shape detection
* - Scaling the returned coordinates to match the displayed image
* - Rendering overlays for detected circles/texts
* - Allowing zoom in/out (via buttons and scroll wheel)
* - Showing a popup when a shape is selected
*/
import React, { useRef } from "react";
import ShapeOverlay from "./ShapeOverlay";
import Popup from "./Popup";
import ZoomControls from "./ZoomControls";
import useZoom from "../hooks/useZoom";
function ImageCanvas({
imageUrl,
imgRef,
setLoaded,
setError,
setImageInfo,
setCircles,
setTexts,
setRawCircles,
setRawTexts,
loaded,
imageInfo,
circles,
texts,
setSelectedShape,
selectedShape,
}) {
const wrapperRef = useRef(null);
const { zoom, zoomIn, zoomOut, handleWheel } = useZoom({ min: 1, max: 3, step: 0.25 });
const handleImageLoad = async () => {
if (!imgRef.current) return;
const info = {
naturalWidth: imgRef.current.naturalWidth,
naturalHeight: imgRef.current.naturalHeight,
clientWidth: imgRef.current.clientWidth,
clientHeight: imgRef.current.clientHeight,
scaleX: imgRef.current.clientWidth / imgRef.current.naturalWidth,
scaleY: imgRef.current.clientHeight / imgRef.current.naturalHeight,
};
setImageInfo(info);
setLoaded(true);
try {
const blob = await fetch(imageUrl).then((r) => r.blob());
const formData = new FormData();
formData.append("file", blob, "image.png");
const res = await fetch("/detect/", {
method: "POST",
body: formData,
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const data = await res.json();
const rawCircles = data.circles || [];
const rawTexts = data.texts || [];
setRawCircles(rawCircles);
setRawTexts(rawTexts);
const scaledCircles = rawCircles.map((c) => ({
...c,
x: c.x * info.scaleX,
y: c.y * info.scaleY,
r: c.r * Math.min(info.scaleX, info.scaleY),
}));
const scaledTexts = rawTexts.map((t) => ({
...t,
x1: t.x1 * info.scaleX,
y1: t.y1 * info.scaleY,
x2: t.x2 * info.scaleX,
y2: t.y2 * info.scaleY,
}));
setCircles(scaledCircles);
setTexts(scaledTexts);
} catch (err) {
setError(`Failed to detect shapes: ${err.message}`);
console.error(err);
}
};
return (
<div style={{ position: "relative", width: "100%", height: "100%" }}>
<ZoomControls zoom={zoom} zoomIn={zoomIn} zoomOut={zoomOut} />
<div
className="image-container"
style={{ position: "relative", display: "inline-block", overflow: "auto" }}
onWheel={handleWheel}
>
<div
ref={wrapperRef}
className="zoom-wrapper"
style={{
position: "relative",
width: imageInfo ? imageInfo.clientWidth : "auto",
height: imageInfo ? imageInfo.clientHeight : "auto",
transform: `scale(${zoom})`,
transformOrigin: "0 0",
transition: "transform 120ms ease-out",
}}
>
<img
ref={imgRef}
src={imageUrl}
alt="uploaded"
onLoad={handleImageLoad}
style={{
width: imageInfo ? imageInfo.clientWidth : "100%",
height: imageInfo ? imageInfo.clientHeight : "auto",
display: "block",
userSelect: "none",
}}
/>
{loaded && imageInfo && (
<ShapeOverlay
imageInfo={imageInfo}
circles={circles}
texts={texts}
setSelectedShape={setSelectedShape}
/>
)}
</div>
</div>
{selectedShape && imageInfo && (
<Popup selectedShape={selectedShape} onClose={() => setSelectedShape(null)} zoom={zoom} />
)}
</div>
);
}
export default ImageCanvas;
|