alex_image_marker
Version:
7 lines (6 loc) • 14.6 kB
JavaScript
"use strict";const M={fillStyle:"#FF000030",lineWidth:1,strokeStyle:"#FF0000"};class x{constructor(t,s,a){this.cancel=()=>{},this.ctxStyle=a,this.scale=t,this.imageMatrix=s,this.id=String(Math.random()).substring(2,8)}setCtxStyle(t,s){this.ctxStyle={...this.ctxStyle,...s},this.setBrushStyle(t)}setBrushStyle(t){const s={...M,...this.ctxStyle};Object.entries(s).forEach(a=>{const[e,i]=a;t[e]=i})}getOriginalPoint(t,s,a){return[Number(((t.offsetX-s.left)/a).toFixed(4)),Number(((t.offsetY-s.top)/a).toFixed(4))]}render(t){this.setBrushStyle(t),this.draw(t),t.fill(),t.stroke()}}const p=class p extends x{constructor(t,s,a){super(s,a,t.style),this.options=t}get data(){const{left:t,top:s,width:a,height:e}=this.options.data;return{left:t*this.scale+this.imageMatrix.left,top:s*this.scale+this.imageMatrix.top,width:a*this.scale,height:e*this.scale}}draw(t){t.beginPath();const{left:s,top:a,width:e,height:i}=this.data;t.rect(s,a,e,i),t.closePath()}updateImageMatrix(t){this.imageMatrix=t}handDrawn(t,s){return new Promise((a,e)=>{const i=n=>{const[r,c]=this.getOriginalPoint(n,this.imageMatrix,this.scale);s.isInImage([n.offsetX,n.offsetY])&&(this.options.data.left=r,this.options.data.top=c,t.canvas.addEventListener("mousemove",h),t.canvas.addEventListener("mouseup",o),t.canvas.addEventListener("mouseleave",o))},h=n=>{if(!s.isInImage([n.offsetX,n.offsetY]))return o();const[c,g]=this.getOriginalPoint(n,this.imageMatrix,this.scale);t.clearRect(),t.drawImage(s),t.ctx.beginPath(),this.options.data.width=c-this.options.data.left,this.options.data.height=g-this.options.data.top,t.redrawGraph()},o=()=>{t.canvas.removeEventListener("mousemove",h),t.canvas.removeEventListener("mouseup",o),t.canvas.removeEventListener("mouseleave",o),this.options.data=this.getRectData(this.options.data),!(this.options.data.width<=1||this.options.data.height<=1)&&(t.canvas.removeEventListener("mousedown",i),a({id:this.id,data:this.options.data}))};t.canvas.addEventListener("mousedown",i),this.cancel=n=>{t.canvas.addEventListener("mousedown",i),e(n)}})}getRectData(t){const{left:s,top:a,width:e,height:i}=t,h=Number((e>=0?s:s+e).toFixed(4)),o=Number((i>=0?a:a+i).toFixed(4)),n=Number(Math.abs(e).toFixed(4)),r=Number(Math.abs(i).toFixed(4));return{left:h,top:o,width:n,height:r}}};p.id="rect";let m=p;const v=class v extends x{constructor(t,s,a){super(s,a,t.style),this.options=t}get data(){return this.options.data.map(t=>{const[s,a]=t;return[s*this.scale+this.imageMatrix.left,a*this.scale+this.imageMatrix.top]})}draw(t){t.beginPath(),this.data.forEach((s,a)=>{const[e,i]=s;a===0?t.moveTo(e,i):t.lineTo(e,i)}),t.closePath()}updateImageMatrix(t){this.imageMatrix=t}handDrawn(t,s){return new Promise((a,e)=>{const i=n=>{const r=this.getOriginalPoint(n,this.imageMatrix,this.scale);s.isInImage([n.offsetX,n.offsetY])&&(this.options.data.length===0?this.options.data.push(r):this.options.data[this.options.data.length-1]=r,this.options.data.push(r))},h=n=>{if(this.options.data.length===0)return;t.clearRect(),t.drawImage(s),t.ctx.beginPath();const r=this.getOriginalPoint(n,this.imageMatrix,this.scale);this.options.data[this.options.data.length-1]=r,t.redrawGraph()},o=n=>{if(s.isInImage([n.offsetX,n.offsetY])){if(this.options.data.length<=4)return console.warn("polygon最少3个点");this.options.data.pop(),this.options.data.pop(),t.canvas.removeEventListener("mousedown",i),t.canvas.removeEventListener("mousemove",h),t.canvas.removeEventListener("dblclick",o),a({id:this.id,data:this.options.data})}};t.canvas.addEventListener("mousedown",i),t.canvas.addEventListener("mousemove",h),t.canvas.addEventListener("dblclick",o),this.cancel=n=>{t.canvas.removeEventListener("mousedown",i),t.canvas.removeEventListener("mousemove",h),t.canvas.removeEventListener("dblclick",o),e(n)}})}};v.id="polygon";let u=v;class E{constructor(t){this.canvas=document.createElement("canvas"),this.MOVE="MOVE",this.DEFAULT="DEFAULT",this.CROSSHAIR="CROSSHAIR",this.graphMap=new Map([["rect",m],["polygon",u]]),this.graphList=[],this.ctx=this.canvas.getContext("2d");const s=document.createElement("div");s.style.width="100%",s.style.height="100%",s.style.overflow="hidden",t.container.appendChild(s),this.canvas.width=s.offsetWidth,this.canvas.height=s.offsetHeight,s.appendChild(this.canvas)}get data(){return this.graphList}graphClass(t){const s=this.graphMap.get(t);if(s)return s;throw new Error(`当前暂不支持${t}类型`)}drawImage(t){const{left:s,top:a,width:e,height:i}=t.matrix;this.ctx.drawImage(t.image,s,a,e,i)}clearRect(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)}redrawGraph(){this.graphList.forEach(t=>t.render(this.ctx))}createGraph(t,s,a,e){const i=this.graphClass(t);return new i(s,a,e)}addGraph(t){return this.graphList.push(t),t}removeGraph(t){this.graphList=this.graphList.filter(s=>t?!t.includes(s.id):!1)}updateImageMatrix(t){this.graphList.forEach(s=>{s.updateImageMatrix(t),s.draw(this.ctx)})}updateImageScale(t){this.graphList.forEach(s=>s.scale=t)}}class S{constructor(t){this.imageScaleValue=1,this.originalWidth=0,this.originalHeight=0,this.width=0,this.height=0,this.left=0,this.top=0,this.options=t,this.image=new Image,this.onLoad=this.createImage()}get matrix(){return{top:this.top,left:this.left,width:this.width,height:this.height}}get scale(){return this.imageScaleValue}createImage(){return new Promise(t=>{this.image.onload=()=>{this.originalWidth=this.image.width,this.originalHeight=this.image.height,this.originalWidth,this.originalHeight;const s=this.options.fit==="contain"?Math.max:Math.min;this.imageScaleValue=s(this.options.container.offsetWidth/this.image.width,this.options.container.offsetHeight/this.image.height),this.width=this.image.width*this.imageScaleValue,this.height=this.image.height*this.imageScaleValue,this.left=this.options.container.offsetWidth/2-this.width/2,this.top=this.options.container.offsetHeight/2-this.height/2,t(this)},this.image.src=this.options.url})}setPosition(t,s){this.left=t,this.top=s}setSize(t,s){this.width=t,this.height=s}setImageLocation(t,s,a,e){const i=(t-this.left)/a,h=(s-this.top)/e;this.left=Math.round(t-this.width*i),this.top=Math.round(s-this.height*h)}setImageScale(t){const{width:s,height:a}=this.matrix;let e=1;if(t==="big"){if(this.imageScaleValue>=this.options.scaleMax)throw"return";e*=1.1}else{if(s<=50)throw"return";e/=1.1}e=Number(e.toFixed(2)),this.imageScaleValue*=e,this.setSize(Math.round(s*e),Math.round(a*e))}isInImage(t){const[s,a]=t,e=s>this.left&&s<this.left+this.width,i=a>this.top&&a<this.top+this.height;return e&&i}}class k{constructor(t,s,a){this.canvasSatrtMovePosition={left:0,top:0,originalLeft:0,originalTop:0},this.mousedown=e=>{this.imageInstance.isInImage([e.offsetX,e.offsetY])&&(this.canvasSatrtMovePosition.left=e.offsetX,this.canvasSatrtMovePosition.top=e.offsetY,this.canvasSatrtMovePosition.originalLeft=this.imageInstance.matrix.left,this.canvasSatrtMovePosition.originalTop=this.imageInstance.matrix.top,this.canvasInstance.canvas.style.cursor=this.canvasInstance.MOVE,this.canvasInstance.canvas.addEventListener("mousemove",this.mousemove),this.canvasInstance.canvas.addEventListener("mouseup",this.mouseup),this.canvasInstance.canvas.addEventListener("mouseleave",this.mouseup))},this.mousemove=e=>{const i=this.canvasSatrtMovePosition.originalLeft+e.offsetX-this.canvasSatrtMovePosition.left,h=this.canvasSatrtMovePosition.originalTop+e.offsetY-this.canvasSatrtMovePosition.top;this.imageInstance.setPosition(i,h),this.canvasInstance.updateImageMatrix(this.imageInstance.matrix),this.redraw()},this.mouseup=()=>{this.canvasInstance.canvas.removeEventListener("mousemove",this.mousemove),this.canvasInstance.canvas.removeEventListener("mouseup",this.mouseup),this.canvasInstance.canvas.removeEventListener("mouseleave",this.mouseup),this.canvasInstance.canvas.style.cursor=this.canvasInstance.DEFAULT},this.wheel=e=>{if(!this.imageInstance.isInImage([e.offsetX,e.offsetY]))return;const{width:h,height:o}=this.imageInstance.matrix,n=e.deltaY<0?"big":"small";try{this.imageInstance.setImageScale(n),this.imageInstance.setImageLocation(e.offsetX,e.offsetY,h,o),this.canvasInstance.updateImageScale(this.imageInstance.scale),this.canvasInstance.updateImageMatrix(this.imageInstance.matrix),this.redraw()}catch{}},this.canvasInstance=t,this.imageInstance=s,this.options=a,this.listenEvent()}listenEvent(){this.options.move&&this.listenMove(),this.options.zoom&&this.listenZoom()}listenMove(){this.canvasInstance.canvas.addEventListener("mousedown",this.mousedown)}listenZoom(){this.canvasInstance.canvas.addEventListener("wheel",this.wheel)}removeListenEvent(){this.options.move&&this.canvasInstance.canvas.removeEventListener("mousedown",this.mousedown),this.options.zoom&&this.canvasInstance.canvas.removeEventListener("wheel",this.wheel)}redraw(){this.canvasInstance.clearRect(),this.canvasInstance.drawImage(this.imageInstance),this.canvasInstance.redrawGraph()}}class y{constructor(t){this.running=!1,this.angle=0,this.config={radius:30,dotCount:8,dotRadius:4,color:"#3498db",fadeColor:"#f3f3f3",speed:2},this.canvasInstance=t}draw(){if(!this.running)return;const t=this.canvasInstance.ctx,{width:s,height:a}=this.canvasInstance.canvas,e=s/2,i=a/2;t.clearRect(0,0,s,a),this.angle+=this.config.speed*.05,this.angle>=Math.PI*2&&(this.angle=0);for(let h=0;h<this.config.dotCount;h++){const o=this.angle+h*(Math.PI*2)/this.config.dotCount,n=e+Math.cos(o)*this.config.radius,r=i+Math.sin(o)*this.config.radius,c=1-h/this.config.dotCount;t.beginPath(),t.arc(n,r,this.config.dotRadius,0,Math.PI*2),t.fillStyle=this.calculateColor(c),t.fill()}requestAnimationFrame(()=>this.draw())}calculateColor(t){const{color:s,fadeColor:a}=this.config;if(!a)return s;const e=g=>{const l=g.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i,(I,f,w,L)=>`#${f}${f}${w}${w}${L}${L}`).substring(1).match(/.{2}/g).map(I=>parseInt(I,16));return[l[0],l[1],l[2]]},[i,h,o]=e(s),[n,r,c]=e(a);return`rgba(
${Math.round(i+(n-i)*(1-t))},
${Math.round(h+(r-h)*(1-t))},
${Math.round(o+(c-o)*(1-t))},
${t.toFixed(2)}
)`}start(){this.running||(this.running=!0,this.draw())}stop(){this.running=!1,this.canvasInstance.ctx.clearRect(0,0,this.canvasInstance.canvas.width,this.canvasInstance.canvas.height)}}const b={rect:{left:0,top:0,width:0,height:0},polygon:[]};class G{constructor(t,s){this.status="wait",this.taskList=[],this.currTaskData=null,this.canvasInstance=t,this.imageInstance=s}get data(){return this.taskList}draw(t,s){return new Promise((a,e)=>{this.canvasInstance.canvas.style.cursor=this.canvasInstance.CROSSHAIR;const i={data:b[t],style:s},h=this.canvasInstance.createGraph(t,i,this.imageInstance.scale,this.imageInstance.matrix);this.taskList.push({type:t,graph:h,resolve:a,reject:e}),this.start()})}start(){if(this.taskList.length===0){this.over();return}if(this.currTaskData)return;console.log("start绘制"),this.currTaskData=this.taskList.shift(),this.status="pending";const{graph:t,resolve:s,reject:a}=this.currTaskData;this.canvasInstance.addGraph(t),t.handDrawn(this.canvasInstance,this.imageInstance).then(s).catch(a).finally(()=>{console.log("绘制结束-成功-失败"),this.currTaskData=null,this.start()})}over(){this.canvasInstance.canvas.style.cursor=this.canvasInstance.DEFAULT,this.status="finish"}delTask(t){this.taskList[t].reject(`取消${this.taskList[t].type}类型绘制`),this.taskList.splice(t,1)}drawCancel(t){if(this.status==="wait"||!this.currTaskData)throw new Error("当前暂无绘制任务!");if(!t||this.currTaskData.type===t){this.currTaskData.graph.cancel(`取消${this.currTaskData.type}类型绘制`),this.canvasInstance.removeGraph([this.currTaskData.graph.id]);return}const s=this.data.findIndex(a=>a.type===t);if(s===-1)throw new Error(`暂无${t}类型绘制任务!`);this.delTask(s)}drawCancelAll(t){if(this.status==="wait"||!this.currTaskData)throw new Error("当前暂无绘制任务!");if(t)this.taskList=this.taskList.filter(s=>s.type!==t);else{this.currTaskData.graph.cancel(`取消${this.currTaskData.type}类型绘制`),this.canvasInstance.removeGraph([this.currTaskData.graph.id]),this.taskList.forEach(s=>s.reject(`取消${s.type}类型绘制`)),this.taskList=[];return}}}class P{constructor(t){this.options={...t,move:t.move??!1,zoom:t.zoom??!1,fit:t.fit??"cover",scaleMax:t.scaleMax??5},this.canvasInstance=new E(this.options),this.canvasLoadingInstance=new y(this.canvasInstance),this.imageInstance=this.createZImage(this.options),this.imageMoveInstance=new k(this.canvasInstance,this.imageInstance,this.options),this.drawGarphInstance=new G(this.canvasInstance,this.imageInstance),this.initImage()}get data(){return this.canvasInstance.data}createZImage(t){this.canvasLoadingInstance.start();const s=new S(t);return s.onLoad.then(()=>this.canvasLoadingInstance.stop()),s}initImage(){this.imageInstance.onLoad.then(()=>{this.canvasInstance.updateImageScale(this.imageInstance.scale),this.canvasInstance.updateImageMatrix(this.imageInstance.matrix),this.redraw()})}updateImage(t){this.imageInstance=this.createZImage({...this.options,url:t}),this.imageInstance.onLoad.then(()=>{this.canvasInstance.updateImageScale(this.imageInstance.scale),this.canvasInstance.updateImageMatrix(this.imageInstance.matrix),this.canvasInstance.clearRect(),this.canvasInstance.removeGraph(),this.canvasInstance.drawImage(this.imageInstance)})}reset(){this.imageInstance=this.createZImage(this.options),this.imageInstance.onLoad.then(()=>{this.canvasInstance.updateImageScale(this.imageInstance.scale),this.canvasInstance.updateImageMatrix(this.imageInstance.matrix),this.redraw()})}redraw(){this.canvasInstance.clearRect(),this.canvasInstance.drawImage(this.imageInstance),this.canvasInstance.redrawGraph()}addGraph(t,s){const a=this.canvasInstance.createGraph(t,s,this.imageInstance.scale,this.imageInstance.matrix);return this.canvasInstance.addGraph(a),this.imageInstance.onLoad.then(this.redraw.bind(this)),a.id}removeGraph(t){this.canvasInstance.removeGraph(t)}setGraphStyle(t,s){const a=this.canvasInstance.data.find(e=>e.id===t);a==null||a.setCtxStyle(this.canvasInstance.ctx,s),this.redraw()}draw(t,s){return new Promise((a,e)=>{this.imageInstance.onLoad.then(()=>{this.drawGarphInstance.status==="wait"&&this.imageMoveInstance.removeListenEvent(),this.drawGarphInstance.draw(t,s).then(a).catch(e).finally(()=>{this.drawGarphInstance.status==="finish"&&(this.imageMoveInstance.listenEvent(),this.drawGarphInstance.status="wait")})})})}drawCancel(t){this.imageInstance.onLoad.then(()=>{this.drawGarphInstance.drawCancel(t)})}drawCancelAll(t){this.imageInstance.onLoad.then(()=>{this.drawGarphInstance.drawCancelAll(t)})}destory(){}}module.exports=P;