From 4fa9c5c6e3fa927792be64c0b9071cd25a1cb380 Mon Sep 17 00:00:00 2001 From: Daniel Reis Date: Thu, 4 Apr 2024 18:16:10 +0100 Subject: [PATCH 1/2] Download image --- src/components/Board/Board.tsx | 91 ++++++++++++++++--- src/components/Board/__docs__/Board.mdx | 3 +- .../Board/__docs__/Board.stories.tsx | 2 +- src/components/Board/__docs__/Example.tsx | 7 +- 4 files changed, 88 insertions(+), 15 deletions(-) diff --git a/src/components/Board/Board.tsx b/src/components/Board/Board.tsx index 5214d5c..26c8ca1 100644 --- a/src/components/Board/Board.tsx +++ b/src/components/Board/Board.tsx @@ -8,7 +8,7 @@ import styled from "styled-components"; export type BoardProps = { primary?: boolean; items: CanvasObject[]; - imageSrc: string; + image: { name: string; src: string }; initialStatus?: { draggingEnabled?: boolean; currentZoom?: number; @@ -30,6 +30,7 @@ export type BoardActions = { toggleDragging: (value?: boolean) => void; resetZoom: () => void; deleteSelectedObjects: () => void; + downloadImage: () => void; }; type CanvasAnnotationState = { @@ -48,7 +49,7 @@ const Board = React.forwardRef( ( { primary = true, - imageSrc, + image, initialStatus, items, onToggleDragging, @@ -80,6 +81,73 @@ const Board = React.forwardRef( editor?.canvas.discardActiveObject(); } }, + downloadImage() { + // Create a temporary canvas to compose original image and annotations + const tempCanvas = document.createElement("canvas"); + const tempCtx = tempCanvas.getContext("2d")!; + + // Get the original image data from the canvas + const originalImageSrc = image.src; // Provide the path to your original image + const originalImage = new Image(); + originalImage.src = originalImageSrc; + + // Wait for the original image to load before composing + originalImage.onload = function () { + // Set the size of the temporary canvas to match the original image + tempCanvas.width = originalImage.width; + tempCanvas.height = originalImage.height; + + // Draw the original image onto the temporary canvas + tempCtx.drawImage(originalImage, 0, 0); + + // Get the Fabric.js canvas instance + // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain + // const canvas = editor?.canvas!; + // const fabricCanvas = canvas.getObjects(); + // console.log(fabricCanvas[0]); + + // items.forEach((item) => { + // const polygon = new fabric.Polygon(item.coords, { + // name: `ID_${item.id}`, + // fill: undefined, + // stroke: "red", + // strokeWidth: 1, + // }); + // // tempCtx.save(); + // polygon.render(tempCtx); + // // tempCtx.restore(); + // }); + + // Loop through all objects on the Fabric.js canvas and draw them onto the temporary canvas + // fabricCanvas.forEach((obj) => { + // const scaleFactorX = tempCanvas.width / canvas.width!; + // const scaleFactorY = tempCanvas.height / canvas.height!; + + // console.log({ scaleFactorX, scaleFactorY }); + + // // Adjust top and left positions based on the scale + // const left = obj.left! * scaleFactorX; + // const top = obj.top! * scaleFactorY; + + // tempCtx.save(); + // tempCtx.translate(0, 0); + // tempCtx.scale(scaleFactorX, scaleFactorY); + // obj.render(tempCtx); + // tempCtx.restore(); + // }); + + // Convert the composed image on the temporary canvas to a data URL + const composedDataURL = tempCanvas.toDataURL("image/png"); + + // Create a temporary anchor element + const link = document.createElement("a"); + link.href = composedDataURL; + link.download = image.name; // Set the desired filename + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + }, })); const { editor, onReady } = useFabricJSEditor(); @@ -121,7 +189,7 @@ const Board = React.forwardRef( editor.canvas.defaultCursor = draggingEnabled ? "pointer" : "default"; fabric.Image.fromURL( - imageSrc, + image.src, (img) => { const { canvas } = editor; const scaleRatio = Math.min( @@ -172,6 +240,8 @@ const Board = React.forwardRef( this.lastPosX = evt.clientX; this.lastPosY = evt.clientY; + const pointer = editor.canvas.getPointer(evt); + console.log(`x: ${pointer.x} y: ${pointer.y}`); opt.e.preventDefault(); opt.e.stopPropagation(); }, @@ -233,14 +303,7 @@ const Board = React.forwardRef( // }); editor.canvas.renderAll(); - }, [ - primary, - draggingEnabled, - editor, - imageSrc, - onLoadedImage, - onZoomChange, - ]); + }, [primary, draggingEnabled, editor, image, onLoadedImage, onZoomChange]); // Update zoom parent value useEffect(() => { @@ -262,8 +325,14 @@ const Board = React.forwardRef( return { x, y }; }; + // Clear all objects from canvas + editor?.canvas?.getObjects().forEach((o) => editor?.canvas?.remove(o)); + editor?.canvas?.discardActiveObject(); + editor?.canvas?.renderAll(); + for (const item of items) { const polygon = new fabric.Polygon(item.coords.map(toScaledCoord), { + name: `ID_${item.id}`, fill: undefined, stroke: "red", strokeWidth: 0.3, diff --git a/src/components/Board/__docs__/Board.mdx b/src/components/Board/__docs__/Board.mdx index 49f8228..ef422fd 100644 --- a/src/components/Board/__docs__/Board.mdx +++ b/src/components/Board/__docs__/Board.mdx @@ -18,12 +18,13 @@ Button component with different props. import Board {BoardActions} from "react-canvas-annotator"; const Example = () => { + const image = {name: "test", src: "http://imagesource.com/test.png"} const ref = React.createRef(); return ( ); diff --git a/src/components/Board/__docs__/Board.stories.tsx b/src/components/Board/__docs__/Board.stories.tsx index ae99c73..50a3fac 100644 --- a/src/components/Board/__docs__/Board.stories.tsx +++ b/src/components/Board/__docs__/Board.stories.tsx @@ -304,7 +304,7 @@ type Story = StoryObj; export const Main: Story = { args: { primary: true, - imageSrc: "holder-min.jpg", + image: { name: "holder-min", src: "holder-min.jpg" }, items: ITEMS, }, }; diff --git a/src/components/Board/__docs__/Example.tsx b/src/components/Board/__docs__/Example.tsx index deb5059..c06d727 100644 --- a/src/components/Board/__docs__/Example.tsx +++ b/src/components/Board/__docs__/Example.tsx @@ -13,7 +13,7 @@ const StyledP = styled.p` padding: 3px; `; -const Example: FC = ({ primary = true, items, imageSrc }) => { +const Example: FC = ({ primary = true, items, image }) => { const ref = React.createRef(); const [toggleStatus, setToggleStatus] = useState(false); @@ -29,6 +29,9 @@ const Example: FC = ({ primary = true, items, imageSrc }) => { + Current zoom: {currentZoom} @@ -46,7 +49,7 @@ const Example: FC = ({ primary = true, items, imageSrc }) => { setToggleStatus(s)} onZoomChange={(v) => setCurrentZoom(v)} From 0482cd060602e96c02c895dfb7069190fcc0a50b Mon Sep 17 00:00:00 2001 From: Daniel Reis Date: Thu, 4 Apr 2024 18:19:13 +0100 Subject: [PATCH 2/2] Fixed test --- src/components/Board/__test__/Board.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Board/__test__/Board.test.tsx b/src/components/Board/__test__/Board.test.tsx index 7842e29..c20717a 100644 --- a/src/components/Board/__test__/Board.test.tsx +++ b/src/components/Board/__test__/Board.test.tsx @@ -6,7 +6,7 @@ import "@testing-library/jest-dom"; // This needs to be here for now. describe("Board component", () => { it("Board should render correctly", () => { - render(); + render(); const board = screen.getByRole("board"); expect(board).toBeInTheDocument(); });