Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 80 additions & 11 deletions src/components/Board/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,6 +30,7 @@ export type BoardActions = {
toggleDragging: (value?: boolean) => void;
resetZoom: () => void;
deleteSelectedObjects: () => void;
downloadImage: () => void;
};

type CanvasAnnotationState = {
Expand All @@ -48,7 +49,7 @@ const Board = React.forwardRef<BoardActions, BoardProps>(
(
{
primary = true,
imageSrc,
image,
initialStatus,
items,
onToggleDragging,
Expand Down Expand Up @@ -80,6 +81,73 @@ const Board = React.forwardRef<BoardActions, BoardProps>(
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();

Expand Down Expand Up @@ -121,7 +189,7 @@ const Board = React.forwardRef<BoardActions, BoardProps>(
editor.canvas.defaultCursor = draggingEnabled ? "pointer" : "default";

fabric.Image.fromURL(
imageSrc,
image.src,
(img) => {
const { canvas } = editor;
const scaleRatio = Math.min(
Expand Down Expand Up @@ -172,6 +240,8 @@ const Board = React.forwardRef<BoardActions, BoardProps>(
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();
},
Expand Down Expand Up @@ -233,14 +303,7 @@ const Board = React.forwardRef<BoardActions, BoardProps>(
// });

editor.canvas.renderAll();
}, [
primary,
draggingEnabled,
editor,
imageSrc,
onLoadedImage,
onZoomChange,
]);
}, [primary, draggingEnabled, editor, image, onLoadedImage, onZoomChange]);

// Update zoom parent value
useEffect(() => {
Expand All @@ -262,8 +325,14 @@ const Board = React.forwardRef<BoardActions, BoardProps>(
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,
Expand Down
3 changes: 2 additions & 1 deletion src/components/Board/__docs__/Board.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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<BoardActions>();
return (
<Board
primary
ref={ref}
imageSrc={"holder-min.jpg"}
image={image}
items={[]}
/>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Board/__docs__/Board.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ type Story = StoryObj<typeof Example>;
export const Main: Story = {
args: {
primary: true,
imageSrc: "holder-min.jpg",
image: { name: "holder-min", src: "holder-min.jpg" },
items: ITEMS,
},
};
7 changes: 5 additions & 2 deletions src/components/Board/__docs__/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const StyledP = styled.p`
padding: 3px;
`;

const Example: FC<BoardProps> = ({ primary = true, items, imageSrc }) => {
const Example: FC<BoardProps> = ({ primary = true, items, image }) => {
const ref = React.createRef<BoardActions>();

const [toggleStatus, setToggleStatus] = useState(false);
Expand All @@ -29,6 +29,9 @@ const Example: FC<BoardProps> = ({ primary = true, items, imageSrc }) => {
<button onClick={() => ref.current?.deleteSelectedObjects()}>
Delete Selected
</button>
<button onClick={() => ref.current?.downloadImage()}>
Download Image
</button>
</StyledDiv>
<StyledDiv>
<StyledP>Current zoom: {currentZoom}</StyledP>
Expand All @@ -46,7 +49,7 @@ const Example: FC<BoardProps> = ({ primary = true, items, imageSrc }) => {
<Board
ref={ref}
primary={primary}
imageSrc={imageSrc}
image={image}
items={items}
onToggleDragging={(s) => setToggleStatus(s)}
onZoomChange={(v) => setCurrentZoom(v)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Board/__test__/Board.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<Board items={[]} imageSrc={""} />);
render(<Board items={[]} image={{ name: "test", src: "test.png" }} />);
const board = screen.getByRole("board");
expect(board).toBeInTheDocument();
});
Expand Down