Skip to content

Commit

Permalink
🔥🐹 ↝ [SSM-79 SSM-80]: Annotation tool is now working (well, img up/do…
Browse files Browse the repository at this point in the history
…wnl w/ annotation is working)
  • Loading branch information
Gizmotronn committed Jan 1, 2025
1 parent bb80d04 commit 2213242
Show file tree
Hide file tree
Showing 15 changed files with 453 additions and 482 deletions.
152 changes: 0 additions & 152 deletions app/starnet/annotation/page.tsx

This file was deleted.

14 changes: 10 additions & 4 deletions app/tests/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
"use client";

import { ImageAnnotator } from "@/components/Projects/(classifications)/Annotating/Annotator";
import React, { useState } from "react";
import StarnetLayout from "@/components/Layout/Starnet";
import FreeformUploadData from "@/components/Projects/(classifications)/FreeForm";
// import { TopographicMap } from "@/components/topographic-map";

export default function TestPage() {
return (
// <StarnetLayout>
<>
{/* <Greenhouse /> */}
{/* <MiningComponent /> */}
<FreeformUploadData />
<main className="container mx-auto py-8">
<div className="flex flex-col items-center gap-6">
<h1 className="text-3xl font-bold">Image Annotator</h1>
<p className="text-muted-foreground text-center max-w-2xl">
Upload an image and use the pen tool to draw annotations. When you&apos;re done, download the annotated image.
</p>
<ImageAnnotator />
</div>
</main>
</>
// {/* </StarnetLayout> */}
);
Expand Down
152 changes: 152 additions & 0 deletions components/Projects/(classifications)/Annotating/Annotator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"use client";

import { useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Download, Upload } from 'lucide-react';
import { ImageUploader } from './ImageUploader';
import { DrawingCanvas } from './DrawingCanvas';
import { DrawingControls } from './DrawingControls';
import { downloadAnnotatedImage } from './DrawingUtils';
import type { Point, Line } from '@/types/Annotation';
import { useToast } from "@/hooks/toast";

export function ImageAnnotator() {
const [imageUrl, setImageUrl] = useState<string | null>(null);
const [isDrawing, setIsDrawing] = useState(false);
const [currentLine, setCurrentLine] = useState<Line>({ points: [], color: '#ff0000', width: 2 });
const [lines, setLines] = useState<Line[]>([]);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [strokeColor, setStrokeColor] = useState('#ff0000');
const [strokeWidth, setStrokeWidth] = useState(2);
const [isDownloading, setIsDownloading] = useState(false);

const svgRef = useRef<SVGSVGElement>(null);
const imageRef = useRef<HTMLImageElement>(null);
const { toast } = useToast();

const handleImageUpload = (file: File) => {
const url = URL.createObjectURL(file);
setImageUrl(url);
setLines([]);
setCurrentLine({ points: [], color: strokeColor, width: strokeWidth });
};

const handleImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
const img = e.currentTarget;
setDimensions({
width: img.naturalWidth,
height: img.naturalHeight
});
};

const handleMouseDown = (point: Point) => {
setIsDrawing(true);
setCurrentLine({
points: [point],
color: strokeColor,
width: strokeWidth
});
};

const handleMouseMove = (point: Point) => {
if (isDrawing) {
setCurrentLine(prev => ({
...prev,
points: [...prev.points, point]
}));
}
};

const handleMouseUp = () => {
if (isDrawing && currentLine.points.length > 0) {
setLines(prev => [...prev, currentLine]);
setCurrentLine({ points: [], color: strokeColor, width: strokeWidth });
}
setIsDrawing(false);
};

const handleDownload = async () => {
if (!svgRef.current || !imageRef.current) {
toast({
title: "Error",
description: "Image or drawing canvas not ready",
variant: "destructive",
});
return;
}

setIsDownloading(true);
try {
await downloadAnnotatedImage(svgRef.current, imageRef.current);
toast({
title: "Success",
description: "Image downloaded successfully",
});
} catch (error) {
toast({
title: "Error",
description: error instanceof Error ? error.message : "Failed to download image",
variant: "destructive",
});
} finally {
setIsDownloading(false);
}
};

return (
<div className="flex flex-col items-center gap-4 p-4">
<div className="flex gap-4">
<ImageUploader onImageUpload={handleImageUpload} />
{imageUrl && (
<Button
variant="outline"
onClick={handleDownload}
disabled={isDownloading}
>
<Download className="mr-2 h-4 w-4" />
{isDownloading ? 'Downloading...' : 'Download'}
</Button>
)}
</div>

{imageUrl && (
<>
<DrawingControls
strokeColor={strokeColor}
strokeWidth={strokeWidth}
onColorChange={setStrokeColor}
onWidthChange={setStrokeWidth}
/>
<div className="relative border rounded-lg overflow-hidden">
<img
ref={imageRef}
src={imageUrl}
alt="Upload an image to annotate"
className="max-w-full h-auto block"
style={{ maxHeight: '70vh' }}
onLoad={handleImageLoad}
/>
<DrawingCanvas
ref={svgRef}
isDrawing={isDrawing}
currentLine={currentLine}
lines={lines}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
width={dimensions.width}
height={dimensions.height}
/>
</div>
</>
)}

{!imageUrl && (
<div className="flex flex-col items-center justify-center w-full h-64 border-2 border-dashed rounded-lg bg-gray-50 dark:bg-gray-800">
<Upload className="h-12 w-12 text-gray-400" />
<p className="mt-2 text-sm text-gray-500">Upload an image to begin annotating</p>
</div>
)}
</div>
);
}
Loading

0 comments on commit 2213242

Please sign in to comment.