UNPKG

vue-pincher

Version:
347 lines (346 loc) 11.1 kB
import { Transition as e, createCommentVNode as t, createElementBlock as n, createElementVNode as r, createVNode as i, defineComponent as a, mergeModels as o, onMounted as s, onUnmounted as c, openBlock as l, useModel as u, useTemplateRef as d, watch as f, withCtx as p } from "vue"; //#region lib/crop.ts function m(e, t, n, r = !1) { let i = { x: e.x + e.width / 2, y: e.y + e.height / 2 }, a = h(t, n), o = n.width / a, s = n.height / a; if (r) { let e = i.x < t.width / 2 ? 1 / 3 : 2 / 3; return { x: i.x - o * e, y: i.y - s * e, width: o, height: s }; } else return { x: i.x - o / 2, y: i.y - s / 2, width: o, height: s }; } function h(e, t) { return Math.max(t.width / e.width, t.height / e.height); } function g(e, t) { return Math.min(t.width / e.width, t.height / e.height); } function _(e) { return e = (e % 360 + 360) % 360, e >= 180 && (e -= 360), e; } function v(e) { return Math.PI / 180 * e; } function y(e) { let t = 0, n = 0; for (let r of e.touches) t += r.clientX, n += r.clientY; let r = e.touches.length; return { x: t / r, y: n / r }; } function b(e) { if (!e.touches?.[0] || !e.touches?.[1]) throw Error("getDistance requires at least two touches"); let t = e.touches[0], n = e.touches[1], r = n.clientX - t.clientX, i = n.clientY - t.clientY; return Math.hypot(r, i); } //#endregion //#region lib/manipulator.ts var x = class { resizeObserver; resetRequested = !1; clientWidth = 0; clientHeight = 0; canvasWidth = 0; canvasHeight = 0; fitWidth = 0; fitHeight = 0; constructor(e, t, n, r, i) { this._ctx = e, this._image = t, this._state = n, this._trimTo = r, this._attention = i, this.adjustSize(), this.resizeObserver = new ResizeObserver(() => { e && requestAnimationFrame(() => { this.adjustSize(), this.resetRequested ? (this.resetRequested = !1, this.reset()) : this.redraw(); }); }), this.resizeObserver.observe(e.canvas), n.scale === 0 && this.reset(); } set image(e) { this._image = e, this.redraw(); } set state(e) { this._state = e, this.redraw(); } set attention(e) { this._attention = e, this.reset(); } set trimTo(e) { this._trimTo = e, this.reset(); } reset(e = !1) { if (e) { this.resetRequested = !0; return; } if (this._trimTo) this.showRegion(this._trimTo, !0); else if (this._attention?.[0]) this.showRegion(m(this._attention[0], this._image, { width: this.canvasWidth, height: this.canvasHeight }, !1)); else { let e = v(this._state.angle), t = Math.abs(Math.cos(e)), n = Math.abs(Math.sin(e)), r = this.fitWidth * t + this.fitHeight * n, i = this.fitHeight * t + this.fitWidth * n; this.manipulate({ scale: h({ width: r, height: i }, { width: this.canvasWidth, height: this.canvasHeight }), offsetX: 0, offsetY: 0 }); } } manipulate(e) { if (!this._ctx || Object.values(e).every((e) => e === void 0)) return; let t = !1; if (e.angle !== void 0 || e.dAngle !== void 0) { let n = _(e.angle ?? this._state.angle + (e.dAngle ?? 0)); this._state.angle !== n && (this._state.angle = n, t = !0); } let n = this._state.scale; if (t || e.scale !== void 0 || e.dScale !== void 0 || e.offsetX !== void 0 || e.dOffsetX !== void 0 || e.offsetY !== void 0 || e.dOffsetY !== void 0) { let r = v(this._state.angle), i = Math.abs(Math.cos(r)), a = Math.abs(Math.sin(r)), o = this.fitWidth * i + this.fitHeight * a, s = this.fitHeight * i + this.fitWidth * a, c = !1; if (t || e.scale !== void 0 || e.dScale !== void 0) { let n = Math.min(this.canvasWidth / o, this.canvasHeight / s), r = e.scale ?? this._state.scale * (e.dScale ?? 1), i = Math.max(n, r); this._state.scale !== i && (this._state.scale = i, c = !0, t = !0); } if (c) { let t = this._state.scale / n, r = e.centerX ?? this.canvasWidth / 2, i = e.centerY ?? this.canvasHeight / 2, a = this.canvasWidth / 2 + this._state.offsetX * this.canvasWidth, o = this.canvasHeight / 2 + this._state.offsetY * this.canvasHeight, s = r - a, c = i - o; this._state.offsetX += (s - s * t) / this.canvasWidth, this._state.offsetY += (c - c * t) / this.canvasHeight; } if (t || e.offsetX !== void 0 || e.dOffsetX !== void 0) { let n = e.offsetX ?? this._state.offsetX + (e.dOffsetX ?? 0) / this.clientWidth, r = Math.abs(this.canvasWidth - o * this._state.scale) / 2 / this.canvasWidth, i = Math.max(-r, Math.min(n, r)); this._state.offsetX !== i && (this._state.offsetX = i, t = !0); } if (t || e.offsetY !== void 0 || e.dOffsetY !== void 0) { let n = e.offsetY ?? this._state.offsetY + (e.dOffsetY ?? 0) / this.clientHeight, r = Math.abs(this.canvasHeight - s * this._state.scale) / 2 / this.canvasHeight, i = Math.max(-r, Math.min(n, r)); this._state.offsetY !== i && (this._state.offsetY = i, t = !0); } } t && this.redraw(); } showRegion(e, t = !1) { let n = g(this._image, { width: this.canvasWidth, height: this.canvasHeight }), r = (t ? h : g)(e, { width: this.canvasWidth, height: this.canvasHeight }), i = e.x + e.width / 2, a = e.y + e.height / 2, o = (this._image.width / 2 - i) * r / this.canvasWidth, s = (this._image.height / 2 - a) * r / this.canvasHeight; this.manipulate({ scale: r / n, offsetX: o, offsetY: s }); } getCurrentRegion() { let e = this._state.scale, t = g(this._image, { width: this.canvasWidth, height: this.canvasHeight }); return { x: (this._image.width * e * t - this.canvasWidth - 2 * this._state.offsetX * this.canvasWidth) / (e * t * 2), y: (this._image.height * e * t - this.canvasHeight - 2 * this._state.offsetY * this.canvasHeight) / (e * t * 2), width: this.canvasWidth / e / t, height: this.canvasHeight / e / t }; } close() { this.resizeObserver.disconnect(); } redraw() { if (!this._ctx) return; let e = this._state.scale, t = this.fitWidth * e, n = this.fitHeight * e; if (this._ctx.save(), this._ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight), this._ctx.translate(this._state.offsetX * this.canvasWidth, this._state.offsetY * this.canvasHeight), this._state.angle !== 0) { let e = this.canvasWidth / 2, t = this.canvasHeight / 2; this._ctx.translate(e, t), this._ctx.rotate(v(this._state.angle)), this._ctx.translate(-e, -t); } this._ctx.drawImage(this._image, (this.canvasWidth - t) / 2, (this.canvasHeight - n) / 2, t, n), this._ctx.restore(); } adjustSize() { if (!this._ctx) return; this.clientWidth = this._ctx.canvas.clientWidth, this.clientHeight = this._ctx.canvas.clientHeight, this.canvasWidth = this._ctx.canvas.width = this.clientWidth * window.devicePixelRatio, this.canvasHeight = this._ctx.canvas.height = this.clientHeight * window.devicePixelRatio; let e = g(this._image, { width: this.canvasWidth, height: this.canvasHeight }); this.fitWidth = this._image.width * e, this.fitHeight = this._image.height * e; } }, S = class { constructor(e) { this.manipulator = e; } wheel(e) { this.manipulator.manipulate({ dScale: Math.sign(e.deltaY) * -.1 + 1, centerX: e.offsetX * window.devicePixelRatio, centerY: e.offsetY * window.devicePixelRatio }); } dblclick() { this.manipulator.reset(); } manipulating = !1; previousClick = null; previousTouch = null; mouseMoving(e) { if (this.manipulating && this.previousClick) { let t = e.clientX - this.previousClick.x, n = e.clientY - this.previousClick.y; this.previousClick = e, this.manipulator.manipulate({ dOffsetX: t, dOffsetY: n }); } } mouseDown(e) { this.manipulating = !0, this.previousClick = e; } mouseUp() { this.manipulating = !1; } touchMoving(e) { if (this.manipulating) { if (e.touches?.[0] && this.previousTouch?.touches?.[0] && e.touches.length === 1) { let t = e.touches[0].clientX - this.previousTouch.touches[0].clientX, n = e.touches[0].clientY - this.previousTouch.touches[0].clientY; this.manipulator.manipulate({ dOffsetX: t, dOffsetY: n }); } else if (e.touches && e.touches.length >= 2 && this.previousTouch && this.previousTouch.touches.length >= 2) { let t = y(this.previousTouch), n = y(e), r = e.target.getBoundingClientRect(), i = n.x - r.x, a = n.y - r.y; this.manipulator.manipulate({ dScale: b(e) / b(this.previousTouch), dOffsetX: n.x - t.x, dOffsetY: n.y - t.y, centerX: i * window.devicePixelRatio, centerY: a * window.devicePixelRatio }); } e.touches && (this.previousTouch = e), e.preventDefault(); } } touchDown(e) { e.touches && (this.manipulating = !0, this.previousTouch = e); } touchUp() { this.manipulating = !1; } }, C = { ref: "canvasRef", class: "fit" }, w = { key: 0, class: "grid-overlay" }, T = /* @__PURE__ */ ((e, t) => { let n = e.__vccOpts || e; for (let [e, r] of t) n[e] = r; return n; })(/* @__PURE__ */ a({ __name: "vue-pincher", props: /* @__PURE__ */ o({ image: {}, readonly: { type: Boolean, default: !1 }, showGrid: { type: Boolean, default: !1 }, trimTo: { default: () => void 0 }, attention: { default: () => [] } }, { modelValue: { default: { angle: 0, offsetX: 0, offsetY: 0, scale: 0 } }, modelModifiers: {} }), emits: ["update:modelValue"], setup(a, { expose: o }) { o({ rotate: y, rotateTo: b, reset: v, showRegion: T }); let m = u(a, "modelValue"), h = d("canvasRef"), g, _; s(() => { if (!h.value) return; let e = h.value.getContext("2d"); e && (g = new x(e, a.image, m.value, a.trimTo, a.attention), a.readonly || (_ = new S(g))); }), c(() => { g?.close(); }), f(() => a.image, (e) => g.image = e), f(() => a.trimTo, (e) => g.trimTo = e), f(() => a.attention, (e) => g.attention = e), f(m, (e) => g.state = e), f(() => [ m.value.angle, m.value.offsetX, m.value.offsetY, m.value.scale ], () => g.redraw()); function v(e = !1) { g.reset(e); } function y(e) { g.manipulate({ dAngle: e }); } function b(e) { g.manipulate({ angle: e }); } function T(e) { g.showRegion(e); } a.readonly || (window.addEventListener("touchmove", D), window.addEventListener("touchend", O), window.addEventListener("mousemove", A), window.addEventListener("mouseup", j), c(() => { window.removeEventListener("touchmove", D), window.removeEventListener("touchend", O), window.removeEventListener("mousemove", A), window.removeEventListener("mouseup", j); })); function E(e) { _?.touchDown(e); } function D(e) { _?.touchMoving(e); } function O() { _?.touchUp(); } function k(e) { _?.mouseDown(e); } function A(e) { _?.mouseMoving(e); } function j() { _?.mouseUp(); } function M(e) { _?.wheel(e); } function N() { _?.dblclick(); } return (o, s) => (l(), n("div", { class: "fit", style: { position: "relative" }, onMousedown: k, onTouchstart: E, onWheel: M, onDblclick: N }, [r("canvas", C, null, 512), i(e, null, { default: p(() => [a.showGrid ? (l(), n("div", w)) : t("", !0)]), _: 1 })], 32)); } }), [["__scopeId", "data-v-5f0c7ad8"]]); //#endregion export { T as default };