UNPKG

alex_image_marker

Version:

466 lines (465 loc) 18.8 kB
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, l] = 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 = l - 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 = /* @__PURE__ */ 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 // 旋转速度 (1-5) }, 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 * 0.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 = (l) => { const g = l.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 [g[0], g[1], g[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 T { 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() { } // 编辑图形 // updateGraph (id: string) { // const graph = this.canvasInstance.data.find(item => item.id === id) // if (!graph) throw new Error(`当前没有id为<${id}>的图形!`) // console.log(graph) // } } export { T as default };