UNPKG

@rockshin/react-image-annotation

Version:

An image annotation tool for ai project that manual annotation for images, easy to use!

144 lines (143 loc) 5.54 kB
import * as __WEBPACK_EXTERNAL_MODULE_tldraw__ from "tldraw"; const getImage = async (src)=>{ let blob; if (src.startsWith('data:')) { const base64Data = src.split(',')[1]; const mimeType = src.split(',')[0].split(':')[1].split(';')[0]; const byteString = atob(base64Data); const arrayBuffer = new ArrayBuffer(byteString.length); const uint8Array = new Uint8Array(arrayBuffer); for(let i = 0; i < byteString.length; i++)uint8Array[i] = byteString.charCodeAt(i); blob = new Blob([ arrayBuffer ], { type: mimeType }); } else blob = await fetch(src).then((res)=>res.blob()); const dataUrl = await __WEBPACK_EXTERNAL_MODULE_tldraw__.FileHelpers.blobToDataUrl(blob); const { w: width, h: height } = await __WEBPACK_EXTERNAL_MODULE_tldraw__.MediaHelpers.getImageSize(blob); return { src: dataUrl, width, height, type: blob.type }; }; function makeSureShapeIsAtBottom(editor, shapeId) { if (!editor) return; const shape = editor.getShape(shapeId); if (!shape) return; const pageId = editor.getCurrentPageId(); if (shape.parentId !== pageId) editor.moveShapesToPage([ shape ], pageId); const siblings = editor.getSortedChildIdsForParent(pageId); const currentBottomShape = editor.getShape(siblings[0]); if (currentBottomShape.id !== shapeId) editor.sendToBack([ shape ]); } const getRectangleAnnotations = (editor)=>{ if (!editor) return []; const annotations = []; const shapes = editor.getCurrentPageShapes(); shapes.forEach((shape)=>{ if ('geo' === shape.type && 'rectangle' === shape.props.geo) { const originalBounds = { x: shape.x, y: shape.y, width: shape.props.w, height: shape.props.h }; const annotation = { id: shape.id, x: originalBounds.x, y: originalBounds.y, width: originalBounds.width, height: originalBounds.height, rotation: shape.rotation || 0, type: 'rectangle', label: shape.props.text || '', timestamp: Date.now(), metadata: { color: shape.props.color || 'default', createdBy: 'user', modifiedAt: Date.now(), version: 1, tags: [], isVerified: false } }; annotations.push(annotation); } }); return annotations; }; const IMAGE_LOAD_TIMEOUT = 30000; const loadImageWithTimeout = async (src)=>{ try { if (src.startsWith('data:image/')) { const [header, content] = src.split(','); if (!header.includes('base64') || !content) throw new Error('Invalid base64 image format'); } else new URL(src); } catch (error) { if (error instanceof Error && 'Invalid base64 image format' === error.message) throw error; throw new Error('Invalid image URL'); } const timeoutPromise = new Promise((_, reject)=>{ setTimeout(()=>reject(new Error('Image load timed out')), IMAGE_LOAD_TIMEOUT); }); try { const result = await Promise.race([ getImage(src), timeoutPromise ]); return result; } catch (error) { if (error instanceof Error) { if ('Image load timed out' === error.message) throw new Error(`Failed to load image within ${IMAGE_LOAD_TIMEOUT / 1000} seconds`); if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) throw new Error('Network error: Unable to load image'); if (error.message.includes('base64')) throw new Error('Invalid base64 image data'); } throw new Error('Failed to load image'); } }; const loadImageForIndex = async ({ imageIndex, images, setIsLoading, setImageLoadError, setImage, setUsedNumbers, setDeletedNumbers, onImageLoadError })=>{ try { setIsLoading(true); setImageLoadError(null); const base64Image = await loadImageWithTimeout(images[imageIndex].src); setImage({ ...base64Image, id: images[imageIndex].id || (0, __WEBPACK_EXTERNAL_MODULE_tldraw__.uniqueId)() }); const newUsedNumbers = new Set(); images[imageIndex].annotations.forEach((annotation)=>{ if (annotation.label) { const num = parseInt(annotation.label); if (!isNaN(num)) newUsedNumbers.add(num); } }); setUsedNumbers(newUsedNumbers); setDeletedNumbers([]); } catch (error) { const err = error instanceof Error ? error : new Error('Failed to load image'); console.error('Failed to load image:', err); setImageLoadError(err); onImageLoadError?.(err, { id: images[imageIndex].id || '', src: images[imageIndex].src, index: imageIndex }); } finally{ setIsLoading(false); } }; const cleanUpEditor = (editor)=>{ editor?.run(()=>{ editor?.deleteShapes(Array.from(editor.getCurrentPageShapeIds())); }, { ignoreShapeLock: true }); }; export { cleanUpEditor, getImage, getRectangleAnnotations, loadImageForIndex, loadImageWithTimeout, makeSureShapeIsAtBottom };