- started painer on video feed will finish

This commit is contained in:
2025-11-21 16:01:34 +00:00
parent 9deeda1bc3
commit 68711b9087
10 changed files with 245 additions and 77 deletions

View File

@@ -1,9 +1,28 @@
import VideoFeedCard from "./VideoFeedCard";
import { useState } from "react";
import VideoFeedGridPainter from "./VideoFeedGridPainter";
import CameraSettings from "./CameraSettings";
import type { Region } from "../../../types/types";
const CameraGrid = () => {
const [regions, setRegions] = useState<Region[]>([
{ name: "Region 1", brushColour: "#ff0000" },
{ name: "Region 2", brushColour: "#00ff00" },
]);
const [selectedRegionIndex, setSelectedRegionIndex] = useState(0);
const updateRegionColour = (index: number, newColour: string) => {
setRegions((prev) => prev.map((r, i) => (i === index ? { ...r, brushColour: newColour } : r)));
};
return (
<div className="grid grid-cols-1 md:grid-cols-2">
<VideoFeedCard />
<VideoFeedGridPainter regions={regions} selectedRegionIndex={selectedRegionIndex} />
<CameraSettings
regions={regions}
selectedRegionIndex={selectedRegionIndex}
onSelectRegion={setSelectedRegionIndex}
onChangeRegionColour={updateRegionColour}
/>
</div>
);
};

View File

@@ -0,0 +1,43 @@
import Card from "../../../ui/Card";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import "react-tabs/style/react-tabs.css";
import RegionSelector from "./RegionSelector";
import type { Region } from "../../../types/types";
type CameraSettingsProps = {
regions: Region[];
selectedRegionIndex: number;
onSelectRegion: (index: number) => void;
onChangeRegionColour: (index: number, colour: string) => void;
};
const CameraSettings = ({
regions,
selectedRegionIndex,
onSelectRegion,
onChangeRegionColour,
}: CameraSettingsProps) => {
return (
<Card className="p-4 min-h-screen">
<Tabs selectedTabClassName="bg-gray-300 text-gray-900 font-semibold border-none rounded-sm">
<TabList>
<Tab>Target Detection</Tab>
<Tab>Camera 1</Tab>
</TabList>
<TabPanel>
<RegionSelector
regions={regions}
selectedRegionIndex={selectedRegionIndex}
onSelectRegion={onSelectRegion}
onChangeRegionColour={onChangeRegionColour}
/>
</TabPanel>
<TabPanel>
<div>Camera details</div>
</TabPanel>
</Tabs>
</Card>
);
};
export default CameraSettings;

View File

@@ -0,0 +1,11 @@
type ColourPickerProps = {
colour: string;
setColour: (colour: string) => void;
};
const ColourPicker = ({ colour, setColour }: ColourPickerProps) => {
console.log(colour);
return <input type="color" name="" id="" value={colour} onChange={(e) => setColour(e.target.value)} />;
};
export default ColourPicker;

View File

@@ -0,0 +1,37 @@
import ColourPicker from "./colourPicker";
import type { Region } from "../../../types/types";
type RegionSelectorProps = {
regions: Region[];
selectedRegionIndex: number;
onSelectRegion: (index: number) => void;
onChangeRegionColour: (index: number, colour: string) => void;
};
const RegionSelector = ({
regions,
selectedRegionIndex,
onSelectRegion,
onChangeRegionColour,
}: RegionSelectorProps) => {
return (
<div>
<div>
<h2>Region Select</h2>
</div>
<div>
{regions.map((region, idx) => (
<div key={region.name}>
<label style={{ marginRight: "0.5rem" }}>
<input type="radio" checked={selectedRegionIndex === idx} onChange={() => onSelectRegion(idx)} />{" "}
{region.name}
</label>
<ColourPicker colour={region.brushColour} setColour={(c: string) => onChangeRegionColour(idx, c)} />
</div>
))}
</div>
</div>
);
};
export default RegionSelector;

View File

@@ -1,13 +0,0 @@
import Card from "../../../ui/Card";
import VideoFeedGridPainter from "./VideoFeedGridPainter";
const VideoFeedCard = () => {
return (
<Card className="w-full md:w-[70%]">
<VideoFeedGridPainter />
</Card>
);
};
export default VideoFeedCard;

View File

@@ -1,74 +1,104 @@
import { useRef, type RefObject } from "react";
import { Stage, Layer, Image, Rect } from "react-konva";
import { Stage, Layer, Image, Shape } from "react-konva";
import type { KonvaEventObject } from "konva/lib/Node";
import { useCreateVideoSnapshot } from "../hooks/useGetvideoSnapshots";
import Card from "../../../ui/Card";
import type { Region } from "../../../types/types";
const VideoFeedGridPainter = () => {
const rows = 40;
const cols = 40;
const size = 20;
const gap = 0;
type VideoFeedGridPainterProps = {
regions: Region[];
selectedRegionIndex: number;
};
const VideoFeedGridPainter = ({ regions, selectedRegionIndex }: VideoFeedGridPainterProps) => {
const { latestBitmapRef } = useCreateVideoSnapshot();
const isDrawing = useRef(false);
const rows = 100;
const cols = 100;
const size = 10;
const gap = 0;
const isDrawingRef = useRef(false);
const paintedCellsRef = useRef<Set<string>>(new Set());
const squares = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
squares.push(
<Rect
key={`${row}-${col}`}
x={col * (size + gap)}
y={row * (size + gap)}
width={size}
height={size}
fill="#ddd"
stroke="black"
strokeWidth={0.5}
opacity={0.5}
/>,
);
}
}
const getCoords = (e) => {
isDrawing.current = true;
};
const handleMouseMove = (e) => {
if (!isDrawing.current) {
return;
}
const pos = e.target.getStage().getPointerPosition();
console.log(pos);
};
const handleMouseUp = () => {
isDrawing.current = false;
};
const paintLayerRef = useRef<any>(null);
const draw = (bmp: RefObject<ImageBitmap | null>) => {
if (!bmp || !bmp.current) {
return;
} else {
const frame = bmp.current;
return frame;
}
if (!bmp || !bmp.current) return null;
return bmp.current;
};
const paintCell = (x: number, y: number) => {
const col = Math.floor(x / (size + gap));
const row = Math.floor(y / (size + gap));
if (row < 0 || row >= rows || col < 0 || col >= cols) return;
const key = `${row}-${col}`;
const set = paintedCellsRef.current;
if (set.has(key)) return;
set.add(key);
paintLayerRef.current?.batchDraw();
};
const handleStageMouseDown = (e: KonvaEventObject<MouseEvent>) => {
if (!selectedRegionIndex) return;
isDrawingRef.current = true;
const pos = e.target.getStage()?.getPointerPosition();
if (pos) paintCell(pos.x, pos.y);
};
const handleStageMouseMove = (e: KonvaEventObject<MouseEvent>) => {
if (!isDrawingRef.current) return;
if (!selectedRegionIndex) return;
const pos = e.target.getStage()?.getPointerPosition();
if (pos) paintCell(pos.x, pos.y);
};
const handleStageMouseUp = () => {
isDrawingRef.current = false;
};
return (
<Stage
width={640}
height={360}
onMouseDown={getCoords}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
<Layer>
<Image image={draw(latestBitmapRef)} width={640} height={360} />
</Layer>
<Layer opacity={0.3}>{squares}</Layer>
</Stage>
<Card className="w-187.5 place-self-start">
<Stage
width={740}
height={460}
onMouseDown={handleStageMouseDown}
onMouseMove={handleStageMouseMove}
onMouseUp={handleStageMouseUp}
onMouseLeave={handleStageMouseUp}
>
<Layer>
<Image image={draw(latestBitmapRef)} width={740} height={460} />
</Layer>
<Layer ref={paintLayerRef} opacity={0.6}>
<Shape
sceneFunc={(ctx, shape) => {
const cells = paintedCellsRef.current;
cells.forEach((key) => {
const [rowStr, colStr] = key.split("-");
const row = Number(rowStr);
const col = Number(colStr);
const x = col * (size + gap);
const y = row * (size + gap);
ctx.beginPath();
ctx.rect(x, y, size, size);
ctx.fillStyle = regions[selectedRegionIndex]?.brushColour;
ctx.fill();
});
ctx.fillStrokeShape(shape);
}}
/>
</Layer>
</Stage>
</Card>
);
};

View File

@@ -13,6 +13,7 @@ export const useCreateVideoSnapshot = () => {
try {
const bitmap = await createImageBitmap(snapShot);
if (!bitmap) return;
latestBitmapRef.current = bitmap;
} catch (error) {
console.log(error);

View File

@@ -10,3 +10,8 @@ export type InfoBarData = {
"memory-cpu-status": string;
"thread-count": string;
};
export type Region = {
name: string;
brushColour: string;
};