luo-image-annotator 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ (function(A,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],t):(A=typeof globalThis<"u"?globalThis:A||self,t(A.VueImageAnnotator={},A.Vue))})(this,(function(A,t){"use strict";var te=Object.defineProperty;var ee=(A,t,V)=>t in A?te(A,t,{enumerable:!0,configurable:!0,writable:!0,value:V}):A[t]=V;var b=(A,t,V)=>ee(A,typeof t!="symbol"?t+"":t,V);const V=(p,e)=>Math.sqrt(Math.pow(p.x-e.x,2)+Math.pow(p.y-e.y,2)),z=(p,e)=>{let o=!1;for(let n=0,l=e.length-1;n<e.length;l=n++){const i=e[n].x,a=e[n].y,s=e[l].x,c=e[l].y;a>p.y!=c>p.y&&p.x<(s-i)*(p.y-a)/(c-a)+i&&(o=!o)}return o},H=(p,e,o)=>{const n=o*(Math.PI/180),l=Math.cos(n),i=Math.sin(n),a=p.x-e.x,s=p.y-e.y;return{x:e.x+(a*l-s*i),y:e.y+(a*i+s*l)}};class J{constructor(e){b(this,"canvas");b(this,"ctx");b(this,"img");b(this,"annotations",[]);b(this,"currentTool",null);b(this,"activeAnnotation",null);b(this,"hoverAnnotation",null);b(this,"isDrawing",!1);b(this,"isDragging",!1);b(this,"isPanning",!1);b(this,"panStartPoint",null);b(this,"dragStartPoint",null);b(this,"dragStartAnnotation",null);b(this,"lastMouseMovePoint",null);b(this,"isHoveringStartPoint",!1);b(this,"currentLabelColor","#FF4081");b(this,"visibleLabels",new Set);b(this,"selectedHandleIndex",-1);b(this,"hoverHandleIndex",-1);b(this,"scale",1);b(this,"offset",{x:0,y:0});b(this,"listeners",{});b(this,"imageUrl","");this.canvas=e;const o=e.getContext("2d");if(!o)throw new Error("Could not get 2d context");this.ctx=o,this.img=new Image,this.img.crossOrigin="Anonymous",this.img.onload=()=>{this.fitImageToCanvas(),this.render()},this.bindEvents()}on(e,o){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].push(o)}emit(e,o){this.listeners[e]&&this.listeners[e].forEach(n=>n(o))}loadImage(e){this.imageUrl=e,this.img.src=e,this.activeAnnotation=null,this.isDrawing=!1}setAnnotations(e){this.annotations=JSON.parse(JSON.stringify(e)),this.render()}getAnnotations(){return this.annotations}setTool(e){e==="pan"?(this.currentTool=null,this.isPanning=!1,this.canvas.style.cursor="grab"):e==="select"?(this.currentTool=null,this.canvas.style.cursor="default"):(this.currentTool=e,this.canvas.style.cursor="crosshair"),this.activeAnnotation=null,this.isDrawing=!1,this.render()}setLabelStyle(e){this.currentLabelColor=e}setVisibleLabels(e){this.visibleLabels=new Set(e),this.render()}zoom(e){const n=e>0?this.scale*1.1:this.scale/1.1;if(n<.1||n>10)return;const l=this.canvas.width/2,i=this.canvas.height/2,a=this.toImageCoords(l,i);this.scale=n,this.offset.x=l-a.x*this.scale,this.offset.y=i-a.y*this.scale,this.render()}resize(){this.fitImageToCanvas(),this.render()}toImageCoords(e,o){return{x:(e-this.offset.x)/this.scale,y:(o-this.offset.y)/this.scale}}toScreenCoords(e,o){return{x:e*this.scale+this.offset.x,y:o*this.scale+this.offset.y}}fitImageToCanvas(){const e=this.canvas.parentElement;if(e){if(this.canvas.width=e.clientWidth,this.canvas.height=e.clientHeight,this.img.width===0)return;const o=this.canvas.width/this.img.width,n=this.canvas.height/this.img.height;this.scale=Math.min(o,n),this.offset.x=(this.canvas.width-this.img.width*this.scale)/2,this.offset.y=(this.canvas.height-this.img.height*this.scale)/2}}bindEvents(){this.canvas.addEventListener("mousedown",this.handleMouseDown.bind(this)),this.canvas.addEventListener("mousemove",this.handleMouseMove.bind(this)),this.canvas.addEventListener("mouseup",this.handleMouseUp.bind(this)),this.canvas.addEventListener("mouseleave",this.handleMouseUp.bind(this)),window.addEventListener("keydown",this.handleKeyDown.bind(this))}handleKeyDown(e){(e.key==="Delete"||e.key==="Backspace")&&this.activeAnnotation&&this.deleteAnnotation(this.activeAnnotation.id)}deleteAnnotation(e){const o=this.annotations.findIndex(n=>n.id===e);if(o>-1){const n=this.annotations[o];this.annotations.splice(o,1),this.activeAnnotation=null,this.emit("annotationChange",{action:"delete",changedItem:n,imageUrl:this.imageUrl}),this.render()}}handleMouseDown(e){const o=this.canvas.getBoundingClientRect(),n=e.clientX-o.left,l=e.clientY-o.top,i=this.toImageCoords(n,l);if(this.canvas.style.cursor==="grab"||this.canvas.style.cursor==="grabbing"){this.isPanning=!0,this.panStartPoint={x:n,y:l},this.canvas.style.cursor="grabbing";return}if(this.activeAnnotation){const c=this.getHitHandle(n,l,this.activeAnnotation);if(c!==-100){this.isDragging=!0,this.dragStartPoint=i,this.selectedHandleIndex=c,this.dragStartAnnotation=JSON.parse(JSON.stringify(this.activeAnnotation));return}}const a=this.getHitCategory(n,l);if(a){this.activeAnnotation=a,this.isDragging=!1,this.selectedHandleIndex=-1,this.emit("annotationChange",{action:"select",changedItem:a,imageUrl:this.imageUrl}),this.render();return}const s=this.getHitAnnotation(i);if(this.currentTool){if(this.isDrawing&&this.currentTool==="polygon"&&this.activeAnnotation){const c=this.activeAnnotation.coordinates;if(c.points.length>2&&V(i,c.points[0])<20/this.scale){this.finishDrawing();return}this.startDrawing(i);return}if(this.currentTool){if(this.currentTool==="polygon"&&!this.isDrawing){this.startDrawing(i);return}if(s){this.activeAnnotation=s,this.isDragging=!0,this.dragStartPoint=i,this.selectedHandleIndex=-1,this.dragStartAnnotation=JSON.parse(JSON.stringify(s)),this.emit("annotationChange",{action:"select",changedItem:s,imageUrl:this.imageUrl}),this.render();return}this.startDrawing(i)}else s?(this.activeAnnotation=s,this.isDragging=!0,this.dragStartPoint=i,this.selectedHandleIndex=-1,this.dragStartAnnotation=JSON.parse(JSON.stringify(s)),this.emit("annotationChange",{action:"select",changedItem:s,imageUrl:this.imageUrl}),this.render()):(this.activeAnnotation=null,this.render())}else if(s){if(!(this.visibleLabels.size>0&&!this.visibleLabels.has(s.label))){this.activeAnnotation=s,this.isDragging=!0,this.dragStartPoint=i,this.selectedHandleIndex=-1,this.dragStartAnnotation=JSON.parse(JSON.stringify(s)),this.emit("annotationChange",{action:"select",changedItem:s,imageUrl:this.imageUrl}),this.render();return}}else this.activeAnnotation=null,this.render()}handleMouseMove(e){const o=this.canvas.getBoundingClientRect(),n=e.clientX-o.left,l=e.clientY-o.top,i=this.toImageCoords(n,l);if(this.isPanning&&this.panStartPoint){const a=n-this.panStartPoint.x,s=l-this.panStartPoint.y;this.offset.x+=a,this.offset.y+=s,this.panStartPoint={x:n,y:l},this.render();return}if(this.lastMouseMovePoint=i,this.isDrawing){if(this.currentTool==="polygon"&&this.activeAnnotation){const a=this.activeAnnotation.coordinates;if(a.points.length>2){const s=a.points[0],c=V(i,s);this.isHoveringStartPoint=c<20/this.scale}else this.isHoveringStartPoint=!1}this.updateDrawing(i)}else this.isDragging&&this.activeAnnotation&&this.dragStartPoint?this.updateDragging(i):this.checkHover(n,l,i);this.render()}handleMouseUp(e){if(this.isPanning){this.isPanning=!1,this.panStartPoint=null,this.canvas.style.cursor="grab";return}if(this.isDrawing){if(this.currentTool==="polygon"){this.render();return}this.finishDrawing()}else this.isDragging&&this.activeAnnotation&&this.emit("annotationChange",{action:"update",changedItem:this.activeAnnotation,imageUrl:this.imageUrl});this.currentTool!=="polygon"&&(this.isDrawing=!1),this.isDragging=!1,this.dragStartPoint=null,this.selectedHandleIndex=-1,this.render()}hexToRgba(e,o){if(!e.startsWith("#"))return e;const n=parseInt(e.slice(1,3),16),l=parseInt(e.slice(3,5),16),i=parseInt(e.slice(5,7),16);return`rgba(${n}, ${l}, ${i}, ${o})`}startDrawing(e){if(!this.currentTool)return;const o=Date.now().toString();if(this.hexToRgba(this.currentLabelColor,.2),this.currentLabelColor,this.currentTool==="rectangle")this.isDrawing=!0,this.dragStartPoint=e,this.activeAnnotation={id:o,type:"rectangle",label:"",coordinates:{x1:e.x,y1:e.y,x2:e.x,y2:e.y},style:{strokeColor:this.currentLabelColor}};else if(this.currentTool==="point"){const n={id:o,type:"point",label:"",coordinates:{points:[e]},style:{strokeColor:this.currentLabelColor}};this.annotations.push(n),this.emit("annotationChange",{action:"add",changedItem:n,imageUrl:this.imageUrl}),this.activeAnnotation=n}else if(this.currentTool==="polygon")this.activeAnnotation&&this.activeAnnotation.type==="polygon"&&this.isDrawing?this.activeAnnotation.coordinates.points.push(e):(this.isDrawing=!0,this.activeAnnotation={id:o,type:"polygon",label:"",coordinates:{points:[e]},style:{strokeColor:this.currentLabelColor}});else if(this.currentTool==="category"){const n={id:o,type:"category",label:"",coordinates:null,style:{strokeColor:this.currentLabelColor}};this.annotations.push(n),this.emit("annotationChange",{action:"add",changedItem:n,imageUrl:this.imageUrl}),this.activeAnnotation=n}else this.currentTool==="rotatedRect"&&(this.isDrawing=!0,this.dragStartPoint=e,this.activeAnnotation={id:o,type:"rotatedRect",label:"",coordinates:{x:e.x,y:e.y,width:0,height:0,angle:0},style:{strokeColor:this.currentLabelColor}})}updateDrawing(e){if(this.activeAnnotation)if(this.activeAnnotation.type==="rectangle"&&this.dragStartPoint){const o=this.activeAnnotation.coordinates;o.x2=e.x,o.y2=e.y}else if(this.activeAnnotation.type==="rotatedRect"&&this.dragStartPoint){const o=this.activeAnnotation.coordinates,n=Math.abs(e.x-this.dragStartPoint.x),l=Math.abs(e.y-this.dragStartPoint.y);o.width=n*2,o.height=l*2}else this.activeAnnotation.type}finishDrawing(){if(this.activeAnnotation){if(this.activeAnnotation.type==="rectangle"){const e=this.activeAnnotation.coordinates;if(Math.abs(e.x1-e.x2)<2||Math.abs(e.y1-e.y2)<2){this.activeAnnotation=null,this.isDrawing=!1;return}const o=Math.min(e.x1,e.x2),n=Math.max(e.x1,e.x2),l=Math.min(e.y1,e.y2),i=Math.max(e.y1,e.y2);e.x1=o,e.x2=n,e.y1=l,e.y2=i,this.annotations.push(this.activeAnnotation),this.emit("annotationChange",{action:"add",changedItem:this.activeAnnotation,imageUrl:this.imageUrl})}else if(this.activeAnnotation.type==="polygon"){if(this.activeAnnotation.coordinates.points.length<3){this.activeAnnotation=null,this.isDrawing=!1;return}this.annotations.push(this.activeAnnotation),this.emit("annotationChange",{action:"add",changedItem:this.activeAnnotation,imageUrl:this.imageUrl})}else if(this.activeAnnotation.type==="rotatedRect"){const e=this.activeAnnotation.coordinates;if(e.width<2||e.height<2){this.activeAnnotation=null,this.isDrawing=!1;return}this.annotations.push(this.activeAnnotation),this.emit("annotationChange",{action:"add",changedItem:this.activeAnnotation,imageUrl:this.imageUrl})}this.activeAnnotation.type}this.isDrawing=!1}updateDragging(e){if(!this.activeAnnotation||!this.dragStartPoint||!this.dragStartAnnotation)return;const o=e.x-this.dragStartPoint.x,n=e.y-this.dragStartPoint.y;this.selectedHandleIndex===-1?this.moveAnnotation(this.activeAnnotation,this.dragStartAnnotation,o,n):this.resizeAnnotation(this.activeAnnotation,this.dragStartAnnotation,this.selectedHandleIndex,e)}moveAnnotation(e,o,n,l){if(e.type==="rectangle"){const i=o.coordinates,a=e.coordinates;a.x1=i.x1+n,a.x2=i.x2+n,a.y1=i.y1+l,a.y2=i.y2+l}else if(e.type==="point"){const i=o.coordinates,a=e.coordinates;a.points=i.points.map(s=>({x:s.x+n,y:s.y+l}))}else if(e.type==="rotatedRect"){const i=o.coordinates,a=e.coordinates;a.x=i.x+n,a.y=i.y+l}else if(e.type==="polygon"){const i=o.coordinates,a=e.coordinates;a.points=i.points.map(s=>({x:s.x+n,y:s.y+l}))}}resizeAnnotation(e,o,n,l){if(e.type==="rectangle"){const i=e.coordinates;n===0&&(i.x1=l.x,i.y1=l.y),n===1&&(i.x2=l.x,i.y1=l.y),n===2&&(i.x2=l.x,i.y2=l.y),n===3&&(i.x1=l.x,i.y2=l.y)}else if(e.type==="polygon"){const i=e.coordinates;n>=0&&n<i.points.length&&(i.points[n]=l)}else if(e.type==="point"){const i=e.coordinates;n>=0&&n<i.points.length&&(i.points[n]=l)}else if(e.type==="rotatedRect"){const i=e.coordinates;if(n===-2){const a=i.x,s=i.y,c=l.x-a,m=l.y-s;let d=Math.atan2(m,c)*180/Math.PI;d+=90,i.angle=d}else{const a=i.angle*Math.PI/180,s=Math.cos(-a),c=Math.sin(-a),m=l.x-i.x,d=l.y-i.y,g=m*s-d*c,f=m*c+d*s;(n===0||n===3)&&(i.width/2,i.width=Math.abs(g)*2),(n===1||n===2)&&(i.width=Math.abs(g)*2),(n===0||n===1)&&(i.height=Math.abs(f)*2),(n===2||n===3)&&(i.height=Math.abs(f)*2)}}}getHitAnnotation(e){for(let o=this.annotations.length-1;o>=0;o--){const n=this.annotations[o];if(this.isPointInAnnotation(e,n))return n}return null}isPointInAnnotation(e,o){if(o.type==="rectangle"){const n=o.coordinates;return e.x>=n.x1&&e.x<=n.x2&&e.y>=n.y1&&e.y<=n.y2}else if(o.type==="polygon"){const n=o.coordinates;return z(e,n.points)}else if(o.type==="rotatedRect"){const n=o.coordinates,l=H(e,{x:n.x,y:n.y},-n.angle),i=n.width/2,a=n.height/2;return l.x>=n.x-i&&l.x<=n.x+i&&l.y>=n.y-a&&l.y<=n.y+a}else if(o.type==="point")return o.coordinates.points.some(l=>V(e,l)<10/this.scale);return!1}getHitHandle(e,o,n){const l=this.getAnnotationHandles(n),i=6;for(let a=0;a<l.length;a++){const s=l[a],c=this.toScreenCoords(s.x,s.y);if(Math.abs(e-c.x)<i&&Math.abs(o-c.y)<i)return n.type==="rotatedRect"&&a===4?-2:a}return-100}getAnnotationHandles(e){if(e.type==="rectangle"){const o=e.coordinates;return[{x:o.x1,y:o.y1},{x:o.x2,y:o.y1},{x:o.x2,y:o.y2},{x:o.x1,y:o.y2}]}else{if(e.type==="polygon")return e.coordinates.points;if(e.type==="point")return e.coordinates.points;if(e.type==="rotatedRect"){const o=e.coordinates,n={x:o.x,y:o.y},l=o.width/2,i=o.height/2,a={x:o.x-l,y:o.y-i},s={x:o.x+l,y:o.y-i},c={x:o.x+l,y:o.y+i},m={x:o.x-l,y:o.y+i},d={x:o.x,y:o.y-i-20/this.scale};return[a,s,c,m,d].map(g=>H(g,n,o.angle))}}return[]}checkHover(e,o,n){const l=this.getHitCategory(e,o);if(l){this.canvas.style.cursor="pointer",this.hoverAnnotation=l;return}if(this.activeAnnotation&&this.getHitHandle(e,o,this.activeAnnotation)!==-100){this.canvas.style.cursor="pointer";return}const i=this.getHitAnnotation(n);i?(this.canvas.style.cursor="move",this.hoverAnnotation=i):(this.canvas.style.cursor="default",this.hoverAnnotation=null)}render(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.img.complete&&this.img.width>0&&this.ctx.drawImage(this.img,this.offset.x,this.offset.y,this.img.width*this.scale,this.img.height*this.scale),this.annotations.forEach(e=>{e!==this.activeAnnotation&&this.drawItem(e,!1)}),this.activeAnnotation&&this.drawItem(this.activeAnnotation,!0),this.renderCategories()}renderCategories(){const e=this.annotations.filter(c=>c.type==="category");if(e.length===0)return;this.ctx.save(),this.ctx.font="14px sans-serif",this.ctx.textBaseline="top";let o=10;const n=10,l=8,i=4,a=24,s=8;e.forEach(c=>{const m=c.label||"Unlabeled",d=this.ctx.measureText(m).width+l*2,g=this.activeAnnotation===c,f=this.hoverAnnotation===c;this.ctx.fillStyle=g?"#E3F2FD":f?"#F5F5F5":"rgba(255, 255, 255, 0.9)",this.ctx.strokeStyle=g?"#2196F3":"#666",this.ctx.lineWidth=g?2:1,this.ctx.beginPath(),this.ctx.rect(o,n,d,a),this.ctx.fill(),this.ctx.stroke(),this.ctx.fillStyle=g?"#1976D2":"#333",this.ctx.fillText(m,o+l,n+i),o+=d+s}),this.ctx.restore()}getHitCategory(e,o){const n=this.annotations.filter(d=>d.type==="category");if(n.length===0)return null;this.ctx.save(),this.ctx.font="14px sans-serif";let l=10;const i=10,a=8,s=24,c=8;let m=null;for(const d of n){const g=d.label||"Unlabeled",f=this.ctx.measureText(g).width+a*2;if(e>=l&&e<=l+f&&o>=i&&o<=i+s){m=d;break}l+=f+c}return this.ctx.restore(),m}drawItem(e,o){var i;if(this.visibleLabels.size>0&&!this.visibleLabels.has(e.label)&&!o)return;this.ctx.save();const n=((i=e.style)==null?void 0:i.strokeColor)||"#FF4081",l=o?"#00E5FF":n;if(this.ctx.strokeStyle=l,this.ctx.lineWidth=2,o?this.ctx.fillStyle="rgba(0, 229, 255, 0.2)":this.ctx.fillStyle=this.hexToRgba(n,.2),e.type==="rectangle"){const a=e.coordinates,s=this.toScreenCoords(a.x1,a.y1),c=this.toScreenCoords(a.x2,a.y2),m=Math.min(s.x,c.x),d=Math.min(s.y,c.y),g=Math.abs(s.x-c.x),f=Math.abs(s.y-c.y);this.ctx.strokeRect(m,d,g,f),this.ctx.fillStyle=o?"rgba(0, 229, 255, 0.2)":"rgba(255, 64, 129, 0.2)",this.ctx.fillRect(m,d,g,f),o&&this.drawHandles(this.getAnnotationHandles(e))}else if(e.type==="polygon"){const a=e.coordinates;if(a.points.length===0){this.ctx.restore();return}this.ctx.beginPath();const s=this.toScreenCoords(a.points[0].x,a.points[0].y);this.ctx.moveTo(s.x,s.y);for(let c=1;c<a.points.length;c++){const m=this.toScreenCoords(a.points[c].x,a.points[c].y);this.ctx.lineTo(m.x,m.y)}if(!this.isDrawing||e!==this.activeAnnotation)this.ctx.closePath();else if(this.lastMouseMovePoint){let c=this.lastMouseMovePoint;if(this.isHoveringStartPoint&&a.points.length>0){c=a.points[0];const d=this.toScreenCoords(a.points[0].x,a.points[0].y);this.ctx.save(),this.ctx.beginPath(),this.ctx.arc(d.x,d.y,10,0,Math.PI*2),this.ctx.fillStyle="rgba(255, 215, 0, 0.6)",this.ctx.strokeStyle="#FFFFFF",this.ctx.lineWidth=2,this.ctx.fill(),this.ctx.stroke(),this.ctx.restore()}const m=this.toScreenCoords(c.x,c.y);this.ctx.lineTo(m.x,m.y)}this.ctx.stroke(),this.ctx.fillStyle=o?"rgba(0, 229, 255, 0.2)":"rgba(255, 64, 129, 0.2)",this.ctx.fill(),o&&this.drawHandles(this.getAnnotationHandles(e))}else if(e.type==="rotatedRect"){const a=e.coordinates;this.ctx.translate(this.toScreenCoords(a.x,a.y).x,this.toScreenCoords(a.x,a.y).y),this.ctx.rotate(a.angle*Math.PI/180);const s=a.width*this.scale,c=a.height*this.scale;this.ctx.strokeRect(-s/2,-c/2,s,c),this.ctx.fillStyle=o?"rgba(0, 229, 255, 0.2)":"rgba(255, 64, 129, 0.2)",this.ctx.fillRect(-s/2,-c/2,s,c),this.ctx.rotate(-a.angle*Math.PI/180),this.ctx.translate(-this.toScreenCoords(a.x,a.y).x,-this.toScreenCoords(a.x,a.y).y),o&&this.drawHandles(this.getAnnotationHandles(e))}else e.type==="point"&&e.coordinates.points.forEach(s=>{const c=this.toScreenCoords(s.x,s.y);this.ctx.beginPath(),this.ctx.arc(c.x,c.y,5,0,Math.PI*2),this.ctx.fillStyle=o?"#00E5FF":n,this.ctx.fill(),this.ctx.stroke()});this.ctx.restore()}drawHandles(e){this.ctx.fillStyle="#FFFFFF",this.ctx.strokeStyle="#000000",this.ctx.lineWidth=1,e.forEach(o=>{const n=this.toScreenCoords(o.x,o.y);this.ctx.fillRect(n.x-4,n.y-4,8,8),this.ctx.strokeRect(n.x-4,n.y-4,8,8)})}}const W={key:0,class:"left-sidebar"},j=["onClick","title"],X={class:"center-area"},Y={key:0,class:"top-bar"},Z={class:"label-selector"},q={class:"tags-row"},K=["onClick"],G={key:0,class:"no-labels"},Q={key:1,class:"batch-nav"},tt=["disabled"],et=["disabled"],nt={key:1,class:"right-sidebar"},ot={class:"label-list"},it={class:"label-row"},st=["onUpdate:modelValue","onChange"],at=["title"],lt=["onClick"],rt={class:"action-icon more-actions"},ct=["onClick"],ht={key:2,class:"modal-overlay"},dt={class:"modal-content"},gt={class:"form-group"},mt={class:"form-group"},ft={class:"color-input-wrapper"},yt=t.defineComponent({__name:"ImageAnnotator",props:{annotationTypes:{default:()=>["rectangle","polygon","point","rotatedRect"]},batchImages:{default:()=>[]},labels:{default:()=>[]},defaultActiveType:{},theme:{default:"light"},readOnly:{type:Boolean,default:!1}},emits:["annotationChange","batchChange","labelChange"],setup(p,{expose:e,emit:o}){const n=p,l=o,i=t.ref(null),a=t.ref(null),s=t.ref(null),c=t.ref(null),m=t.ref(0),d=t.ref([]),g=t.ref(""),f=t.ref(!1),y=t.ref({name:"",color:"#FF0000"}),k=t.computed(()=>n.annotationTypes.filter(r=>r!=="category")),v=r=>({rectangle:"Crop",polygon:"Connection",point:"Aim",rotatedRect:"RefreshRight",category:"PriceTag"})[r]||r,I=r=>({rectangle:"矩形框",polygon:"多边形",point:"关键点",rotatedRect:"旋转矩形",category:"分类标签"})[r]||r;t.onMounted(()=>{if(i.value){s.value=new J(i.value),s.value.on("annotationChange",h=>{if(h.action==="add"&&h.changedItem){const u=d.value.find(C=>C.id===g.value);u&&(h.changedItem.label=u.name)}l("annotationChange",h)}),E(),_();const r=new ResizeObserver(()=>{var h;(h=s.value)==null||h.resize()});a.value&&r.observe(a.value)}});const _=()=>{if(!s.value)return;const r=d.value.find(u=>u.id===g.value);r&&s.value.setLabelStyle(r.color);const h=d.value.filter(u=>u.visible).map(u=>u.name);s.value.setVisibleLabels(h)},E=()=>{if(s.value&&n.batchImages.length>0){const r=n.batchImages[m.value];s.value.loadImage(r.imageUrl),r.annotations&&s.value.setAnnotations(r.annotations)}},B=r=>{var h,u;if(c.value=r,r!=="pan"&&r!=="select"&&d.value.length===0){alert("请先创建标签!");return}r==="pan"||r==="select"?(h=s.value)==null||h.setTool(r):(u=s.value)==null||u.setTool(r)},N=()=>{var r;(r=s.value)!=null&&r.activeAnnotation&&s.value.deleteAnnotation(s.value.activeAnnotation.id)},D=()=>{var r;return(r=s.value)==null?void 0:r.zoom(1)},R=()=>{var r;return(r=s.value)==null?void 0:r.zoom(-1)},Rt=()=>{y.value={name:"",color:"#2196F3"},f.value=!0},U=()=>{f.value=!1},Ut=()=>{if(!y.value.name.trim()){alert("请输入标签名称");return}const h={id:Date.now().toString(),name:y.value.name,color:y.value.color,visible:!0};d.value.push(h),l("labelChange",d.value),d.value.length===1&&T(h),U()},T=r=>{var h;g.value=r.id,(h=s.value)==null||h.setLabelStyle(r.color)},Ot=(r,h)=>{if(!r.startsWith("#"))return r;let u=0,C=0,w=0;return r.length===4?(u=parseInt(r[1]+r[1],16),C=parseInt(r[2]+r[2],16),w=parseInt(r[3]+r[3],16)):r.length===7&&(u=parseInt(r.slice(1,3),16),C=parseInt(r.slice(3,5),16),w=parseInt(r.slice(5,7),16)),`rgba(${u}, ${C}, ${w}, ${h})`},zt=r=>{var h;if(r.id===g.value&&((h=s.value)==null||h.setLabelStyle(r.color)),s.value){const u=s.value.getAnnotations();let C=!1;u.forEach(w=>{w.label===r.name&&(w.style||(w.style={}),w.style.strokeColor=r.color,w.style.fillColor=Ot(r.color,.2),C=!0)}),C&&s.value.render()}l("labelChange",d.value)},Jt=r=>{r.visible=!r.visible,_()},Wt=r=>{const h=d.value.findIndex(u=>u.id===r);h>-1&&(d.value.splice(h,1),l("labelChange",d.value),g.value===r&&(g.value=d.value.length>0?d.value[0].id:"",g.value&&T(d.value[0])),_())};t.watch(()=>n.labels,r=>{const h=JSON.parse(JSON.stringify(r||[]));if(d.value=h,d.value.length>0)if(!g.value||!d.value.find(u=>u.id===g.value))T(d.value[0]);else{const u=d.value.find(C=>C.id===g.value);u&&T(u)}else g.value="";_()},{immediate:!0,deep:!0});const jt=()=>{m.value>0&&(L(),m.value--,E(),M())},Xt=()=>{m.value<n.batchImages.length-1&&(L(),m.value++,E(),M())},L=()=>{if(s.value){const r=s.value.getAnnotations();n.batchImages[m.value].annotations=r}},M=()=>{const r=n.batchImages[m.value];l("batchChange",{currentIndex:m.value,total:n.batchImages.length,currentImageUrl:r.imageUrl,currentAnnotations:r.annotations||[]})};return e({jumpTo:r=>{r>=0&&r<n.batchImages.length&&(L(),m.value=r,E(),M())},getAllAnnotations:()=>n.batchImages,getCurrentAnnotation:()=>{var r;return{imageUrl:n.batchImages[m.value].imageUrl,annotations:((r=s.value)==null?void 0:r.getAnnotations())||[]}}}),(r,h)=>{const u=t.resolveComponent("Rank"),C=t.resolveComponent("el-icon"),w=t.resolveComponent("Pointer"),Yt=t.resolveComponent("ZoomIn"),Zt=t.resolveComponent("ZoomOut"),O=t.resolveComponent("Delete"),qt=t.resolveComponent("Back"),Kt=t.resolveComponent("Right"),Gt=t.resolveComponent("View"),Qt=t.resolveComponent("Hide");return t.openBlock(),t.createElementBlock("div",{class:t.normalizeClass(["annotation-container",p.theme])},[p.readOnly?t.createCommentVNode("",!0):(t.openBlock(),t.createElementBlock("div",W,[t.createElementVNode("div",{class:t.normalizeClass(["tool-btn",{active:c.value==="pan"}]),onClick:h[0]||(h[0]=x=>B("pan")),title:"拖动"},[t.createVNode(C,null,{default:t.withCtx(()=>[t.createVNode(u)]),_:1})],2),t.createElementVNode("div",{class:t.normalizeClass(["tool-btn",{active:c.value==="select"}]),onClick:h[1]||(h[1]=x=>B("select")),title:"选择"},[t.createVNode(C,null,{default:t.withCtx(()=>[t.createVNode(w)]),_:1})],2),h[4]||(h[4]=t.createElementVNode("div",{class:"divider"},null,-1)),(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(k.value,x=>(t.openBlock(),t.createElementBlock("div",{key:x,class:t.normalizeClass(["tool-btn",{active:c.value===x}]),onClick:S=>B(x),title:I(x)},[t.createVNode(C,null,{default:t.withCtx(()=>[(t.openBlock(),t.createBlock(t.resolveDynamicComponent(v(x))))]),_:2},1024)],10,j))),128)),h[5]||(h[5]=t.createElementVNode("div",{class:"divider"},null,-1)),t.createElementVNode("div",{class:"tool-btn",onClick:D,title:"放大"},[t.createVNode(C,null,{default:t.withCtx(()=>[t.createVNode(Yt)]),_:1})]),t.createElementVNode("div",{class:"tool-btn",onClick:R,title:"缩小"},[t.createVNode(C,null,{default:t.withCtx(()=>[t.createVNode(Zt)]),_:1})]),h[6]||(h[6]=t.createElementVNode("div",{class:"divider"},null,-1)),t.createElementVNode("div",{class:"tool-btn",onClick:N,title:"删除选中"},[t.createVNode(C,null,{default:t.withCtx(()=>[t.createVNode(O)]),_:1})])])),t.createElementVNode("div",X,[p.readOnly?t.createCommentVNode("",!0):(t.openBlock(),t.createElementBlock("div",Y,[t.createElementVNode("div",Z,[h[7]||(h[7]=t.createElementVNode("span",{class:"label-text"},"当前标签:",-1)),t.createElementVNode("div",q,[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(d.value,x=>(t.openBlock(),t.createElementBlock("div",{key:x.id,class:t.normalizeClass(["tag-chip",{active:g.value===x.id}]),style:t.normalizeStyle({backgroundColor:x.color,borderColor:x.color}),onClick:S=>T(x)},t.toDisplayString(x.name),15,K))),128)),d.value.length===0?(t.openBlock(),t.createElementBlock("div",G,"请在右侧创建标签")):t.createCommentVNode("",!0)])])])),t.createElementVNode("div",{class:"canvas-wrapper",ref_key:"canvasWrapper",ref:a},[t.createElementVNode("canvas",{ref_key:"canvasRef",ref:i},null,512)],512),p.batchImages&&p.batchImages.length>0?(t.openBlock(),t.createElementBlock("div",Q,[t.createElementVNode("button",{onClick:jt,disabled:m.value<=0},[t.createVNode(C,null,{default:t.withCtx(()=>[t.createVNode(qt)]),_:1}),h[8]||(h[8]=t.createTextVNode(" 上一张 ",-1))],8,tt),t.createElementVNode("span",null,t.toDisplayString(m.value+1)+" / "+t.toDisplayString(p.batchImages.length),1),t.createElementVNode("button",{onClick:Xt,disabled:m.value>=p.batchImages.length-1},[h[9]||(h[9]=t.createTextVNode(" 下一张 ",-1)),t.createVNode(C,null,{default:t.withCtx(()=>[t.createVNode(Kt)]),_:1})],8,et)])):t.createCommentVNode("",!0)]),p.readOnly?t.createCommentVNode("",!0):(t.openBlock(),t.createElementBlock("div",nt,[t.createElementVNode("div",{class:"sidebar-header"},[h[10]||(h[10]=t.createElementVNode("h3",null,"标签管理",-1)),t.createElementVNode("button",{class:"add-btn",onClick:Rt},"添加标签")]),t.createElementVNode("div",ot,[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(d.value,x=>(t.openBlock(),t.createElementBlock("div",{key:x.id,class:"label-item"},[t.createElementVNode("div",it,[t.createElementVNode("label",{class:"color-wrapper",style:t.normalizeStyle({backgroundColor:x.color})},[t.withDirectives(t.createElementVNode("input",{type:"color","onUpdate:modelValue":S=>x.color=S,onChange:S=>zt(x),style:{visibility:"hidden",width:"0",height:"0"}},null,40,st),[[t.vModelText,x.color]])],4),t.createElementVNode("span",{class:"label-name",title:x.name},t.toDisplayString(x.name),9,at),t.createElementVNode("span",{class:"action-icon eye",onClick:S=>Jt(x)},[x.visible?(t.openBlock(),t.createBlock(C,{key:0},{default:t.withCtx(()=>[t.createVNode(Gt)]),_:1})):(t.openBlock(),t.createBlock(C,{key:1},{default:t.withCtx(()=>[t.createVNode(Qt)]),_:1}))],8,lt),t.createElementVNode("div",rt,[h[11]||(h[11]=t.createElementVNode("span",{class:"dots"},"•••",-1)),t.createElementVNode("span",{class:"delete-btn",onClick:S=>Wt(x.id),title:"删除"},[t.createVNode(C,null,{default:t.withCtx(()=>[t.createVNode(O)]),_:1})],8,ct)])])]))),128))])])),f.value?(t.openBlock(),t.createElementBlock("div",ht,[t.createElementVNode("div",dt,[h[14]||(h[14]=t.createElementVNode("h3",null,"新增标签",-1)),t.createElementVNode("div",gt,[h[12]||(h[12]=t.createElementVNode("label",null,"名称",-1)),t.withDirectives(t.createElementVNode("input",{"onUpdate:modelValue":h[2]||(h[2]=x=>y.value.name=x),placeholder:"请输入标签名称",class:"modal-input"},null,512),[[t.vModelText,y.value.name]])]),t.createElementVNode("div",mt,[h[13]||(h[13]=t.createElementVNode("label",null,"颜色",-1)),t.createElementVNode("div",ft,[t.withDirectives(t.createElementVNode("input",{type:"color","onUpdate:modelValue":h[3]||(h[3]=x=>y.value.color=x),class:"modal-color-picker"},null,512),[[t.vModelText,y.value.color]]),t.createElementVNode("span",null,t.toDisplayString(y.value.color),1)])]),t.createElementVNode("div",{class:"modal-actions"},[t.createElementVNode("button",{onClick:U,class:"cancel-btn"},"取消"),t.createElementVNode("button",{onClick:Ut,class:"confirm-btn"},"确认")])])])):t.createCommentVNode("",!0)],2)}}}),P=(p,e)=>{const o=p.__vccOpts||p;for(const[n,l]of e)o[n]=l;return o},$=P(yt,[["__scopeId","data-v-d3dc5a63"]]),pt={class:"thumbnail-wrapper",ref:"wrapper"},xt=["src","alt"],ut=["viewBox"],bt=["x","y","width","height","stroke"],Ct=["points","stroke"],kt=["x","y","fill"],At={key:1,class:"loading-placeholder"},vt=P(t.defineComponent({__name:"AnnotationThumbnail",props:{src:{},annotations:{},alt:{},labels:{}},setup(p){const e=p,o=t.ref(null),n=t.ref(!1),l=t.ref(0),i=t.ref(0),a=()=>{o.value&&(l.value=o.value.naturalWidth,i.value=o.value.naturalHeight,n.value=!0)},s=g=>{var f;if((f=g.style)!=null&&f.strokeColor)return g.style.strokeColor;if(e.labels){const y=e.labels.find(k=>k.name===g.label);if(y)return y.color}return"#FF0000"},c=g=>{const f=g.coordinates,y=Math.min(f.x1,f.x2),k=Math.min(f.y1,f.y2),v=Math.abs(f.x1-f.x2),I=Math.abs(f.y1-f.y2);return{x:y,y:k,width:v,height:I}},m=g=>g.coordinates.points.map(y=>`${y.x},${y.y}`).join(" "),d=g=>{if(g.type==="rectangle"){const f=c(g);return{x:f.x,y:f.y-5}}else if(g.type==="polygon"){const f=g.coordinates.points;if(f.length>0)return{x:f[0].x,y:f[0].y-5}}return{x:0,y:0}};return(g,f)=>(t.openBlock(),t.createElementBlock("div",pt,[t.createElementVNode("img",{ref_key:"img",ref:o,src:p.src,class:"thumbnail-image",onLoad:a,alt:p.alt},null,40,xt),n.value?(t.openBlock(),t.createElementBlock("svg",{key:0,class:"annotation-overlay",viewBox:`0 0 ${l.value} ${i.value}`,preserveAspectRatio:"none"},[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(p.annotations,y=>(t.openBlock(),t.createElementBlock(t.Fragment,{key:y.id},[y.type==="rectangle"?(t.openBlock(),t.createElementBlock("rect",{key:0,x:c(y).x,y:c(y).y,width:c(y).width,height:c(y).height,stroke:s(y),"stroke-width":"2",fill:"transparent"},null,8,bt)):t.createCommentVNode("",!0),y.type==="polygon"?(t.openBlock(),t.createElementBlock("polygon",{key:1,points:m(y),stroke:s(y),"stroke-width":"2",fill:"transparent"},null,8,Ct)):t.createCommentVNode("",!0),y.label?(t.openBlock(),t.createElementBlock("text",{key:2,x:d(y).x,y:d(y).y,fill:s(y),"font-size":"14","font-weight":"bold",class:"anno-label"},t.toDisplayString(y.label),9,kt)):t.createCommentVNode("",!0)],64))),128))],8,ut)):(t.openBlock(),t.createElementBlock("div",At,"Loading..."))],512))}}),[["__scopeId","data-v-78bcbe0c"]]),wt={class:"batch-annotator"},Nt={key:0,class:"gallery-view"},Vt={class:"gallery-header"},_t={class:"label-summary"},Et={class:"gallery-grid"},St=["onClick"],It={class:"thumbnail-wrapper"},Bt={class:"img-meta"},Dt={class:"img-index"},Tt={class:"anno-count"},Pt={class:"bottom-bar"},Lt={key:1,class:"editor-view"},Mt={class:"editor-header"},Ht={class:"header-left"},$t={class:"editor-title"},Ft={class:"editor-content"},F=P(t.defineComponent({__name:"BatchAnnotator",props:{images:{},labels:{}},emits:["export","update:images"],setup(p,{emit:e}){const o=p,n=e,l=t.ref("gallery"),i=t.ref([]),a=t.ref(0),s=t.ref(null);t.watch(()=>o.images,k=>{i.value=JSON.parse(JSON.stringify(k))},{immediate:!0,deep:!0});const c=k=>{a.value=k,l.value="editor",t.nextTick(()=>{s.value&&s.value.jumpTo&&s.value.jumpTo(k)})},m=()=>{if(s.value&&s.value.getCurrentAnnotation){const k=s.value.getCurrentAnnotation();i.value[a.value]&&(i.value[a.value].annotations=k.annotations)}l.value="gallery"},d=()=>{n("export",i.value)},g=k=>{a.value=k.currentIndex,i.value[k.currentIndex]&&(i.value[k.currentIndex].annotations=k.currentAnnotations)},f=k=>{if(s.value&&s.value.getCurrentAnnotation){const v=s.value.getCurrentAnnotation();i.value[a.value]&&(i.value[a.value].annotations=v.annotations,n("update:images",i.value))}},y=k=>{};return(k,v)=>{const I=t.resolveComponent("Edit"),_=t.resolveComponent("el-icon"),E=t.resolveComponent("Download"),B=t.resolveComponent("Back");return t.openBlock(),t.createElementBlock("div",wt,[l.value==="gallery"?(t.openBlock(),t.createElementBlock("div",Nt,[t.createElementVNode("div",Vt,[t.createElementVNode("h3",null,"批量查看与标注 ("+t.toDisplayString(i.value.length)+" 张)",1),t.createElementVNode("div",_t,[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(p.labels,N=>(t.openBlock(),t.createElementBlock("span",{key:N.id,class:"label-badge",style:t.normalizeStyle({backgroundColor:N.color})},t.toDisplayString(N.name),5))),128))])]),t.createElementVNode("div",Et,[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(i.value,(N,D)=>(t.openBlock(),t.createElementBlock("div",{key:D,class:"gallery-item",onClick:R=>c(D)},[t.createElementVNode("div",It,[t.createVNode(vt,{src:N.imageUrl,annotations:N.annotations||[],labels:p.labels},null,8,["src","annotations","labels"])]),t.createElementVNode("div",Bt,[t.createElementVNode("span",Dt,"#"+t.toDisplayString(D+1),1),t.createElementVNode("span",Tt,t.toDisplayString((N.annotations||[]).length)+" 标注",1)])],8,St))),128))]),t.createElementVNode("div",Pt,[t.createElementVNode("button",{class:"action-btn primary",onClick:v[0]||(v[0]=N=>c(0))},[t.createVNode(_,null,{default:t.withCtx(()=>[t.createVNode(I)]),_:1}),v[1]||(v[1]=t.createTextVNode(" 手动标注 ",-1))]),t.createElementVNode("button",{class:"action-btn success",onClick:d},[t.createVNode(_,null,{default:t.withCtx(()=>[t.createVNode(E)]),_:1}),v[2]||(v[2]=t.createTextVNode(" 导出 ",-1))])])])):(t.openBlock(),t.createElementBlock("div",Lt,[t.createElementVNode("div",Mt,[t.createElementVNode("div",Ht,[t.createElementVNode("button",{class:"back-btn",onClick:m},[t.createVNode(_,null,{default:t.withCtx(()=>[t.createVNode(B)]),_:1}),v[3]||(v[3]=t.createTextVNode(" 返回列表 ",-1))]),t.createElementVNode("span",$t,"正在标注: "+t.toDisplayString(a.value+1)+" / "+t.toDisplayString(i.value.length),1)])]),t.createElementVNode("div",Ft,[t.createVNode($,{ref_key:"annotatorRef",ref:s,batchImages:i.value,labels:p.labels,annotationTypes:["rectangle","polygon","point","rotatedRect"],onBatchChange:g,onAnnotationChange:f,onLabelChange:y},null,8,["batchImages","labels"])])]))])}}}),[["__scopeId","data-v-4cf50076"]]);A.BatchAnnotator=F,A.ImageAnnotator=$,A.default=F,Object.defineProperties(A,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
package/dist/vite.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "luo-image-annotator",
3
+ "version": "0.0.1",
4
+ "description": "A simple image annotation component for Vue 3",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "type": "module",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "main": "./dist/luo-image-annotator.umd.js",
15
+ "module": "./dist/luo-image-annotator.es.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/luo-image-annotator.es.js",
21
+ "require": "./dist/luo-image-annotator.umd.js"
22
+ },
23
+ "./style.css": "./dist/style.css"
24
+ },
25
+ "peerDependencies": {
26
+ "vue": "^3.2.0",
27
+ "element-plus": "^2.0.0",
28
+ "@element-plus/icons-vue": "^2.0.0"
29
+ },
30
+ "author": "Luo Luo",
31
+ "license": "MIT",
32
+ "keywords": [
33
+ "vue3",
34
+ "image-annotation",
35
+ "canvas",
36
+ "typescript"
37
+ ],
38
+ "dependencies": {},
39
+ "devDependencies": {
40
+ "@types/node": "^25.4.0",
41
+ "@vitejs/plugin-vue": "^5.2.4",
42
+ "@vue/tsconfig": "^0.9.0",
43
+ "typescript": "^5.9.3",
44
+ "vite": "^6.4.1",
45
+ "vite-plugin-dts": "^4.5.4",
46
+ "vue": "^3.5.30",
47
+ "vue-tsc": "^3.2.5"
48
+ }
49
+ }