alex_image_marker
Version:
466 lines (465 loc) • 18.8 kB
JavaScript
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
};