Твоя задача - сделать аналог кода, но на Python. Я хочу задавать точки на изображении, а также координаты их перемещения, в соответствии с этим необходимо делать distortion изображения. Вот некоторый код на js:
(function() {
const t = document.createElement("link").relList;
if (t && t.supports && t.supports("modulepreload"))
return;
for (const o of document.querySelectorAll('link[rel="modulepreload"]'))
i(o);
new MutationObserver(o=>{
for (const n of o)
if (n.type === "childList")
for (const r of n.addedNodes)
r.tagName === "LINK" && r.rel === "modulepreload" && i(r)
}
).observe(document, {
childList: !0,
subtree: !0
});
function e(o) {
const n = {};
return o.integrity && (n.integrity = o.integrity),
o.referrerpolicy && (n.referrerPolicy = o.referrerpolicy),
o.crossorigin === "use-credentials" ? n.credentials = "include" : o.crossorigin === "anonymous" ? n.credentials = "omit" : n.credentials = "same-origin",
n
}
function i(o) {
if (o.ep)
return;
o.ep = !0;
const n = e(o);
fetch(o.href, n)
}
}
)();
const h = window.devicePixelRatio && window.devicePixelRatio > 1 ? window.devicePixelRatio : 1
, J = typeof FileReader < "u"
, ot = "draggable"in document.createElement("span")
, j = !!navigator.share;
function rt() {
const s = !!navigator.mediaDevices && !!navigator.mediaDevices.enumerateDevices;
if (!s)
return Promise.resolve(!1);
if (s)
return navigator.mediaDevices.enumerateDevices().then(t=>{
let e = !1;
return t.forEach(i=>{
i && i.kind && i.kind === "videoinput" && (e = !0)
}
),
e
}
);
if (navigator.getUserMedia)
return Promise.resolve(!0)
}
function at(s=null, t="auto") {
return s || (s = {
video: !0,
audio: !1
}),
navigator.mediaDevices ? navigator.mediaDevices.getUserMedia(s).then(e=>(e.getVideoTracks().forEach(o=>{
if (o.getCapabilities) {
const n = o.getCapabilities();
n.facingMode && n.facingMode.length > 0 && (currentCameraDirection = n.facingMode[0])
}
}
),
e)) : Promise.reject(new Error("Can't access camera."))
}
function Y() {
return document.readyState === "complete"
}
function ct() {
return Y() ? Promise.resolve() : new Promise(s=>{
let t = setInterval(()=>{
clearInterval(t),
Y() && s()
}
, 50)
}
)
}
const Q = "ontouchstart"in document.documentElement;
function lt(s, t) {
const e = URL.createObjectURL(s)
, i = document.createElement("a");
i.href = e,
i.download = t,
document.body.appendChild(i),
i.click(),
document.body.removeChild(i),
setTimeout(()=>{
URL.revokeObjectURL(e)
}
, 3e3)
}
const G = "distort-grid";
function ht(s, t) {
return Z(s, t)
}
function Z(s, t) {
if (localStorage) {
const e = tt();
s && (typeof t > "u" ? delete e[s] : e[s] = t,
localStorage.setItem(G, JSON.stringify(e)))
}
}
function tt(s) {
if (localStorage) {
const t = localStorage.getItem(G);
let e = {};
if (t)
try {
e = JSON.parse(t)
} catch {
e = {}
}
return s ? e[s] : e
}
}
class X {
constructor() {
this.events = {}
}
on(t, e, i, ...o) {
typeof this.events[t] > "u" && (this.events[t] = []),
this.events[t].push({
scope: i,
callback: e,
args: o
})
}
off(t, e, i) {
if (typeof this.events[t] > "u")
return;
const o = n=>n.scope !== i || n.callback !== e;
this.events[t] = this.events[t].filter(o)
}
has(t, e, i) {
if (typeof this.events[t] > "u")
return !1;
let o = this.events[t].length;
if (e === void 0 && i === void 0)
return o > 0;
const n = r=>{
const a = i ? r.scope === i : !0
, d = r.callback === e;
if (a && d)
return !0
}
;
return this.events[t].some(n)
}
emit(t, ...e) {
if (typeof this.events[t] > "u")
return;
const i = this.events[t].slice();
for (let o in i) {
const n = i[o];
n && n.callback && n.callback.apply(n.scope, [...e, ...n.args])
}
}
}
const l = new X;
class dt {
constructor() {
this.wasInitialized = !1,
this.image = new Image,
this.image.addEventListener("load", this.imageLoaded.bind(this)),
l.on("image:src", this.setSrc, this)
}
imageLoaded() {
l.emit("image:loadcomplete"),
l.emit("image:load", this.image),
this.wasInitialized && l.emit("close-intro"),
this.wasInitialized = !0
}
setSrc(t) {
this.image.src = t,
this.wasInitialized && this.image.naturalWidth !== void 0 && this.image.naturalWidth !== 0 && setTimeout(()=>{
this.imageLoaded()
}
, 100)
}
}
const ut = ["image/png", "image/jpg", "image/jpeg"];
class mt {
constructor() {
J && (this.reader = new FileReader,
l.on("dragdrop:file", this.loadFile, this),
l.on("import:file", this.loadFile, this))
}
loadFile(t) {
if (t && t.type && ut.includes(t.type)) {
l.emit("file:readstart", t),
this.reader.readAsDataURL(t);
const e = i=>{
this.fileLoaded(t, i),
this.reader.removeEventListener("load", e)
}
;
this.reader.addEventListener("load", e, !1)
}
}
fileLoaded(t, e) {
l.emit("file:readcomplete", t, e.target.result),
l.emit("image:src", e.target.result)
}
}
class gt {
constructor() {
ot && (document.addEventListener("drop", this.dropped.bind(this), !1),
document.addEventListener("dragover", t=>t.preventDefault(), !1),
document.addEventListener("dragleave", t=>t.preventDefault(), !1))
}
dropped(t) {
t.preventDefault(),
t.dataTransfer && t.dataTransfer.files && t.dataTransfer.files[0] && (l.emit("dragdrop:file", t.dataTransfer.files[0]),
l.emit("close-intro"))
}
}
class ft extends X {
constructor(t={}) {
super(),
this.id = Math.random(),
this._state = t || {},
this._rafIds = {};
const e = this
, i = {
set(o, n, r) {
const a = o[n];
return o[n] = r,
typeof e._rafIds[n] !== void 0 && cancelAnimationFrame(e._rafIds[n]),
e._rafIds[n] = requestAnimationFrame(()=>{
e.emit("state.update", e._state),
l.emit("update:" + n, r, a),
delete e._rafIds[n]
}
),
!0
}
};
this.state = new Proxy(this._state,i)
}
update(t, e) {
typeof t == "string" && (this.state[t] = e),
typeof t == "object" && Object.keys(t).forEach(i=>{
this.state[i] = t[i]
}
)
}
}
function pt(s) {
if (!(s instanceof Image))
throw new Error("imgEl is not an image",s);
return {
width: s.width || s.naturalWidth,
height: s.height || s.naturalHeight
}
}
function V(s) {
return new Promise(t=>{
const e = new FileReader;
e.onloadend = ()=>t(e.result),
e.readAsDataURL(s)
}
)
}
function vt(s, t) {
return fetch(s).then(e=>e.blob()).then(e=>new File([e],t,{
type: "image/png"
}))
}
function yt(s, t, e) {
const i = et(t, e);
return it(s, i)
}
function et(s, t) {
return {
x: t,
y: t,
width: s.width - t,
height: s.height - t
}
}
function it(s, t) {
let e = 1;
if (s.width > t.width || s.height > t.height) {
const r = t.width / s.width
, a = t.height / s.height;
e = Math.min(a, r)
}
const i = {
width: Math.round(s.width * e),
height: Math.round(s.height * e)
}
, o = (t.width - i.width) / 2
, n = (t.height - i.height) / 2;
return {
...i,
x: o,
y: n,
ratio: e
}
}
function Et(s, t, e) {
return new Promise((i,o)=>{
try {
s.toBlob(n=>i(n))
} catch (n) {
o(n)
}
}
)
}
function D(s, t) {
return s.filter(e=>e.x === t.x && e.y === t.y)[0]
}
function st(s, t) {
return s.filter(e=>e.id === t)[0]
}
function xt(s, t, e) {
const i = et(t, e)
, o = it(s, i)
, n = []
, r = Math.floor(o.width / e)
, a = Math.ceil(o.height / e)
, d = (t.width - r * e) / 2
, v = (t.height - a * e) / 2;
for (let m = 0; m <= a; m++)
for (let y = 0; y <= r; y++)
n.push({
x: y * e,
y: m * e,
id: grid-point-${n.length}
});
const p = [];
for (let m = 0; m < r; ++m)
for (let y = 0; y < a; ++y) {
const b = [D(n, {
x: m * e,
y: y * e
}).id, D(n, {
x: m * e + e,
y: y * e
}).id, D(n, {
x: m * e + e,
y: y * e + e
}).id, D(n, {
x: m * e,
y: y * e + e
}).id];
p.push(b)
}
return {
points: n,
rects: p,
offset: {
x: d,
y: v
},
gridDimensions: {
width: a * e,
height: r * e
},
gridRows: a,
gridColumns: r
}
}
function U(s) {
return typeof s != "object" && typeof s != "function" || s === null
}
class $ {
constructor() {
this.childBranches = new WeakMap,
this.primitiveKeys = new Map,
this.hasValue = !1,
this.value = void 0
}
has(t) {
const e = U(t) ? this.primitiveKeys.get(t) : t;
return e ? this.childBranches.has(e) : !1
}
get(t) {
const e = U(t) ? this.primitiveKeys.get(t) : t;
return e ? this.childBranches.get(e) : void 0
}
resolveBranch(t) {
if (this.has(t))
return this.get(t);
const e = new $
, i = this.createKey(t);
return this.childBranches.set(i, e),
e
}
setValue(t) {
return this.hasValue = !0,
this.value = t
}
createKey(t) {
if (U(t)) {
const e = {};
return this.primitiveKeys.set(t, e),
e
}
return t
}
clear() {
this.childBranches = new WeakMap,
this.primitiveKeys.clear(),
this.hasValue = !1,
this.value = void 0
}
}
function C(s) {
const t = new $;
function e(...i) {
const o = i.reduce((r,a)=>r.resolveBranch(a), t);
if (o.hasValue)
return o.value;
const n = s(...i);
return o.setValue(n)
}
return [e, t.clear.bind(t)]
}
const [S,bt] = C(xt)
, [It,wt] = C(pt);
function St() {
bt(),
wt()
}
l.on("image:loadcomplete", St);
const u = new ft({
containerRect: {
x: 0,
y: 0,
width: 0,
height: 0
},
image: null,
fileName: "",
distortedPoints: [],
shares: {},
showSharesCollection: !1,
get imageSize() {
return u.state.image ? It(u.state.image) : {
width: 0,
height: 0
}
},
get rects() {
if (!u.state.image)
return [];
const {rects: s} = S(u.state.imageSize, c.containerRect, c.gridSize);
return s
},
get points() {
if (!u.state.imageSize)
return [];
const {points: s} = S(u.state.imageSize, c.containerRect, c.gridSize);
return s
},
get offset() {
if (!u.state.imageSize)
return {
x: 0,
y: 0
};
const {offset: s} = S(u.state.imageSize, c.containerRect, c.gridSize);
return s
},
get gridDimensions() {
if (!u.state.imageSize)
return {
width: 0,
height: 0
};
const {gridDimensions: s} = S(u.state.imageSize, c.containerRect, c.gridSize);
return s
},
get gridColumns() {
if (!u.state.imageSize)
return 0;
const {gridColumns: s} = S(u.state.imageSize, c.containerRect, c.gridSize);
return s
},
get gridRows() {
if (!u.state.imageSize)
return 0;
const {gridRows: s} = S(u.state.imageSize, c.containerRect, c.gridSize);
return s
}
})
, c = u.state;
function Ct(s, t, e, i, o, n) {
let r = (s - t) / (e - t) * (o - i) + i;
return n && (i > o ? r = Math.min(Math.max(r, o), i) : r = Math.min(Math.max(r, i), o)),
r
}
function B(s, t, e) {
return t > e ? Math.min(Math.max(s, e), t) : Math.min(Math.max(s, t), e)
}
function Pt(s, t, e=!1) {
const i = s + Math.random() * (t - s);
return e ? Math.round(i) : i
}
function q(s, t) {
const e = s.x - t.x
, i = s.y - t.y;
return Math.sqrt(e * e + i * i)
}
function F(s, t, e, i, o, n, r, a, d) {
const v = ((n - d) * (t - o) - (e - n) * (o - a)) / ((i - r) * (t - o) - (s - i) * (o - a))
, p = ((n - d) * (s - i) - (e - n) * (i - r)) / ((o - a) * (s - i) - (t - o) * (i - r))
, m = e - s * v - t * p;
return [v, p, m]
}
class Rt {
constructor() {
this.values = {},
this.animationFrameId = null;
const t = document.querySelector("#controls");
this.controlEls = Array.from(t.querySelectorAll("[data-control-id]")),
u.on("state.update", e=>this.stateUpdated(e), this),
requestAnimationFrame(()=>{
this.setInitalState()
}
),
this.controlEls.forEach(e=>{
e.addEventListener("change", this.controlUpdated.bind(this), !1),
e.addEventListener("input", this.controlUpdated.bind(this), !1)
}
),
this.randomButtonEl = document.querySelector("#random-button"),
this.randomButtonEl.addEventListener("click", ()=>{
this.randomize()
}
)
}
setInitalState() {
const t = this.controlEls.reduce((e,i)=>{
const o = i.getAttribute("data-control-id");
return typeof e[o] > "u" && (e[o] = this.getInputValue(i)),
e
}
, {});
u.update(t)
}
stateUpdated(t) {
Object.keys(t).forEach(e=>{
this.getInputEls(e).forEach(i=>{
i[this.getValueAttr(i)] !== t[e] && (i[this.getValueAttr(i)] = t[e])
}
)
}
)
}
controlUpdated(t) {
if (Array.isArray(t))
t.forEach(i=>this.controlUpdated(i));
else {
let e = t;
cancelAnimationFrame(this.animationFrameId),
t.target && (e = t.target),
u.update(this.getInputKey(e.id), this.getInputValue(e))
}
}
randomize() {
u.update(this.randomValues)
}
getInputEls(t) {
return this.controlEls.filter(e=>e.getAttribute("data-control-id") === t)
}
getInputKey(t) {
return t.replace("-slider", "").replace("-number", "").replace("-input", "")
}
setControlValues(t) {
const e = {};
for (let i in t) {
const o = this.getInputEls(i);
this.setInputValue(o, t[i]),
this.controlUpdated(o),
e[this.getInputKey(i)] = t[i]
}
this.values = e,
l.emit("control-updated", this.values)
}
setInputValue(t, e) {
if (Array.isArray(t)) {
const i = t
, o = this.getValueAttr(i);
i.forEach(n=>{
n[o] !== e && (n[o] = e)
}
)
} else {
const i = t;
i[this.getValueAttr(i)] = e
}
}
getInputValue(t) {
let e = t[this.getValueAttr(t)];
return ["number", "range"].includes(t.getAttribute("type")) && (e = parseInt(e, 10)),
["checkbox"].includes(t.getAttribute("type")) && (e = !!e),
e
}
getValueAttr(t) {
return t.type === "checkbox" ? "checked" : "value"
}
get constraints() {
const t = {};
return this.numberInputEls.forEach(e=>{
const i = this.getInputKey(e.id);
t[i] = {
min: parseInt(e.min, 10),
max: parseInt(e.max, 10)
}
}
),
t
}
get numberInputEls() {
return this.controlEls.filter(t=>t.type === "number")
}
get checkboxInputEls() {
return this.controlEls.filter(t=>t.type === "checkbox")
}
get randomValues() {
const t = {}
, e = ["gridSize"];
for (let i of e) {
const o = this.constraints[i];
t[i] = Pt(o.min, o.max, !0)
}
return t
}
}
const Lt = u.state;
class kt {
constructor() {
this.exportBtnEl = document.getElementById("export-button"),
this.exportBtnEl.addEventListener("click", this.exportBtnClicked.bind(this), !1)
}
exportBtnClicked() {
l.emit("export-requested", t=>{
lt(t, Lt.fileName ?? "distorted-image.png")
}
)
}
}
class Tt {
constructor() {
J && (this.fileReader = new FileReader,
this.importInputEl = document.getElementById("import-input"),
this.importInputEl.addEventListener("change", this.fileSelected.bind(this), !1))
}
fileSelected(t) {
t && t.target && t.target.files && t.target.files[0] && (l.emit("import:file", t.target.files[0]),
l.emit("close-intro"))
}
}
const _ = u.state;
class At {
constructor() {
this.shareWrapperEl = document.querySelector(".sn-share"),
this.step1El = document.querySelector('[data-share-step="1"]'),
this.step2El = document.querySelector('[data-share-step="2"]'),
this.uploadBtn = document.getElementById("share-upload-btn"),
this.imgurTitleInputEl = document.getElementById("imgur-title"),
this.imgurDescriptionInputEl = document.getElementById("imgur-description"),
this.defaultImgurTitle = "Distorted Image",
this.defaultImgurDescription = "Created with snorpey's image distortion tool https://snorpey.github.io/distort-grid",
this.imgurButtonEl = document.getElementById("imgur-button"),
this.imgurURLInputEl = document.getElementById("imgur-url-input"),
this.imgurURLLinkEl = document.getElementById("imgur-url-link"),
this.imgurURLErrorEl = document.getElementById("imgur-url-error"),
this.twitterLinkEl = document.getElementById("twitter-link"),
this.facebookLinkEl = document.getElementById("facebook-link"),
this.redditLinkEl = document.getElementById("reddit-link"),
this.sharesCollectioBtnEl = document.getElementById("shares-collection-button"),
this.shareCheckboxEl = document.querySelector("#is-showing-share"),
this.nativeShareBtnEl = document.getElementById("native-share-btn"),
j && (this.nativeShareBtnEl.removeAttribute("visibility", "hidden"),
this.nativeShareBtnEl.addEventListener("click", ()=>this.shareNatively())),
Array.from(document.querySelectorAll('[data-callback="startover"]')).forEach(t=>{
t.addEventListener("click", ()=>this.startOver())
}
),
this.isUploading = !1,
this.uploadBtn.addEventListener("click", this.uploadClicked.bind(this), !1),
this.imgurURLInputEl.addEventListener("click", this.selectInput.bind(this), !1),
this.imgurTitleInputEl.addEventListener("focus", ()=>{
this.imgurTitleInputEl.value === this.defaultImgurTitle && (this.imgurTitleInputEl.value = "")
}
),
this.imgurTitleInputEl.addEventListener("blur", ()=>{
this.imgurTitleInputEl.value === "" && (this.imgurTitleInputEl.value = this.defaultImgurTitle)
}
),
this.imgurDescriptionInputEl.addEventListener("focus", ()=>{
this.imgurDescriptionInputEl.value === this.defaultImgurDescription && (this.imgurDescriptionInputEl.value = "")
}
),
this.imgurDescriptionInputEl.addEventListener("blur", ()=>{
this.imgurDescriptionInputEl.value === "" && (this.imgurDescriptionInputEl.value = this.defaultImgurDescription)
}
),
l.on("share:imgur", this.shareImgurData.bind(this))
}
uploadClicked() {
this.isUploading || l.emit("export-requested", this.upload.bind(this))
}
selectInput() {
this.imgurURLInputEl.select()
}
upload(t) {
this.isUploading || (this.isUploading = !0,
V(t).then(e=>{
this.shareWrapperEl.setAttribute("data-current-step", "loading");
const i = this.imgurTitleInputEl.value || null
, o = this.imgurDescriptionInputEl.value || null;
return fetch("https://api.imgur.com/3/image.json", {
method: "POST",
headers: {
Authorization: Client-ID ${atob("YTRjMjQzODBkODg0OTMy")},
"Content-Type": "application/json"
},
body: JSON.stringify({
image: e.split(",")[1],
type: "base64",
title: i,
description: o
}),
type: "json",
crossOrigin: !0
}).then(n=>n.json())
}
).then(e=>{
if (e && e.success)
this.imageUploaded(e);
else {
let i = "An error occured. Please try again later.";
e && e.data && e.data.error && typeof e.data.error == "string" && (i = JSON.parse(JSON.stringify(e.data.error))),
this.imgurURLErrorEl.textContent = i,
this.uploadFailed()
}
}
))
}
imageUploaded(t) {
if (this.isUploading = !1,
t && t.data && t.data.link && t.data.id) {
const e = {};
e[t.data.id] = t.data,
.shares = {
....shares,
...e
},
ht("shares", _.shares),
this.shareImgurData(t.data)
} else
this.uploadFailed()
}
uploadFailed(t) {
this.isUploading = !1,
this.shareWrapperEl.setAttribute("data-current-step", "error")
}
shareImgurData(t) {
this.shareCheckboxEl.checked = !0,
this.shareWrapperEl.setAttribute("data-current-step", "share");
const e = "https://snorpey.github.io/distort-grid"
, i = t.title ?? "Distortion!"
, o = t.description ?? "🖼🛠Check out what I made with @snorpey’s image distortion tool"
, n = t.link
, r = ${o}: ${n} ${e} #distortion
, a = https://www.facebook.com/sharer/sharer.php?s=100&&p[url]=${n}&p[title]=${i}&p[images][0]=${n}&p[summary]=${encodeURIComponent(o)};
this.imgurButtonEl.classList.remove("is-uploading"),
this.imgurURLInputEl.setAttribute("value", n),
this.imgurURLLinkEl.href = n,
this.twitterLinkEl.href = https://twitter.com/intent/tweet?text=${encodeURIComponent(r)},
this.facebookLinkEl.href = a,
this.redditLinkEl.href = https://www.reddit.com/submit?url=${encodeURIComponent(n)}&title=${i}
}
startOver() {
this.shareWrapperEl.setAttribute("data-current-step", "form")
}
shareNatively() {
j && l.emit("export-requested", t=>{
V(t).then(e=>{
const i = e.split(",")[1];
vt(i, this.imgurTitleInputEl.value + ".png").then(o=>navigator.share({
files: [o],
title: this.imgurTitleInputEl.value,
text: this.imgurDescriptionInputEl.value
}))
}
)
}
)
}
}
class Dt {
constructor() {
this.camButtonEl = document.getElementById("cam-button"),
rt().then(t=>{
t && (this.camButtonEl.removeAttribute("visibility"),
this.camButtonEl.addEventListener("click", ()=>this.toggleCameraDisplay()),
this.overlayEl = document.querySelector(".sn-overlay--cam"),
this.shutterBtnEl = document.querySelector("#cam_shutter"),
this.closeBtnEl = document.querySelector("#cam_close"),
this.videoEl = document.querySelector("#cam_video"),
this.shutterBtnEl.addEventListener("click", ()=>this.takePicture()),
this.closeBtnEl.addEventListener("click", ()=>this.closeCamera()),
this.stream = null)
}
)
}
toggleCameraDisplay() {
this.stream ? this.closeCamera() : this.openCamera()
}
openCamera() {
document.documentElement.classList.add("sn-overlay__container--open"),
this.overlayEl.removeAttribute("aria-hidden"),
at().then(t=>{
this.stream = t,
this.videoEl.srcObject = t
}
)
}
closeCamera() {
this.stream && (this.videoEl.srcObject = null,
this.stream.getTracks().forEach(t=>t.stop()),
this.stream = null),
document.documentElement.classList.remove("sn-overlay__container--open"),
this.overlayEl.setAttribute("aria-hidden", "true")
}
takePicture() {
if (this.stream) {
const t = document.createElement("canvas");
t.width = this.videoEl.clientWidth,
t.height = this.videoEl.clientHeight,
t.getContext("2d").drawImage(this.videoEl, 0, 0);
const i = t.toDataURL("image/png");
l.emit("image:src", i),
this.closeCamera()
}
}
}
function nt(s={}) {
return JSON.parse(JSON.stringify(s))
}
class Bt extends X {
constructor({el: t, triggerOnHover: e=!1, direction: i="all"}) {
super(),
this.el = t,
this.movements = {},
this.direction = i,
this.el.addEventListener("mousedown", this.mouseDown.bind(this)),
this.el.addEventListener("mouseup", this.mouseUp.bind(this)),
this.el.addEventListener("mouseleave", this.mouseUp.bind(this)),
this.el.addEventListener("mousemove", this.mouseMove.bind(this)),
Q && (this.el.addEventListener("touchstart", this.touchStart.bind(this)),
this.el.addEventListener("touchend", this.touchEnd.bind(this))),
e && this.el.addEventListener("mouseenter", this.start.bind(this))
}
mouseDown(t) {
this.start(t, "mouse", t)
}
mouseMove(t) {
this.move(t, "mouse", t)
}
mouseUp(t) {
this.end(t, "mouse", t)
}
touchStart(t) {
Array.from(t.changedTouches).forEach(e=>{
this.start(e, e.identifier, t)
}
)
}
touchEnd(t) {
Array.from(t.changedTouches).forEach(e=>{
this.end(e, e.identifier, t)
}
)
}
touchMove(t) {
Array.from(t.changedTouches).forEach(e=>{
this.move(e, e.identifier, t)
}
)
}
start(t, e=null, i) {
i.preventDefault(),
e = e || M(t);
const o = z(t)
, n = {
pageX: t.pageX,
pageY: t.pageY,
clientX: t.clientX,
clientX: t.clientX,
offsetX: o.offsetX,
offsetY: o.offsetY,
x: o.offsetX,
y: o.offsetY,
time: Date.now()
}
, r = this.el
, a = N(i)
, d = Object.keys(this.movements).length;
if (!this.movements[e]) {
const v = nt(n)
, p = O(v, n)
, m = function(b) {
a === "touchmove" ? this.touchMove(b) : this.mouseMove(b)
}
.bind(this);
this.movements[e] = {
startPos: v,
currentPos: n,
handler: m
},
this.el.addEventListener(a, m);
const y = {
id: e,
target: r,
startPos: v,
currentPos: n,
delta: p,
touchCount: d,
timeSinceStart: 0
};
this.emit("dragstart", y),
this.emit("drag", y)
}
this.emit("move", {
target: r,
currentPos: n,
touchCount: d
})
}
move(t, e=null) {
e = e || M(t);
const i = z(t)
, o = {
pageX: t.pageX,
pageY: t.pageY,
clientX: t.clientX,
clientX: t.clientX,
offsetX: i.offsetX,
offsetY: i.offsetY,
x: i.offsetX,
y: i.offsetY,
time: Date.now()
}
, n = this.el
, r = Object.keys(this.movements).length;
if (this.movements[e]) {
const a = this.movements[e].startPos
, d = O(a, o);
this.movements[e].currentPos = o;
const v = o.time - a.time;
this.emit("drag", {
id: e,
target: n,
startPos: a,
currentPos: o,
delta: d,
touchCount: r,
timeSinceStart: v
})
} else
this.emit("move", {
target: n,
currentPos: o,
touchCount: r
})
}
end(t, e=null, i) {
e = M(t);
const o = this.el
, n = z(t)
, r = {
pageX: t.pageX,
pageY: t.pageY,
clientX: t.clientX,
clientX: t.clientX,
offsetX: n.offsetX,
offsetY: n.offsetY,
x: n.offsetX,
y: n.offsetY,
time: Date.now()
}
, a = Object.keys(this.movements).length;
if (this.movements[e]) {
const d = this.movements[e].startPos
, v = O(d, r);
this.movements[e].currentPos = r,
this.el.removeEventListener(N(i), this.movements[e].handler),
delete this.movements[e];
const p = {
id: e,
target: o,
startPos: d,
currentPos: r,
delta: v,
touchCount: a
};
this.emit("drag", p),
this.emit("dragend", p)
}
this.emit("move", {
target: o,
currentPos: r,
touchCount: a
})
}
get canMoveHorizontally() {
return this.direction === "all" || this.direction === "x" || this.direction === "horizontal"
}
get canMoveVertically() {
return this.direction === "all" || this.direction === "y" || this.direction === "vertical"
}
}
function M(s) {
return s instanceof MouseEvent ? "mouse" : s.identifier
}
function N(s) {
return s instanceof MouseEvent ? "mousemove" : Q && s instanceof TouchEvent ? "touchmove" : ""
}
function O(s, t) {
return {
x: t.pageX - s.pageX,
y: t.pageY - s.pageY,
time: t.time - s.time,
distance: q(t, s)
}
}
function z(s) {
const e = (s.target || s.srcElement).getBoundingClientRect()
, i = s.clientX - e.left
, o = s.clientY - e.top;
return {
offsetX: i,
offsetY: o
}
}
function W(s, t) {
return t.indexOf(s) !== -1
}
const [K,H] = C(st);
class Ft extends Bt {
constructor(t={}) {
const e = document.querySelector(".sn-workspace__grid");
super({
el: e
}),
this.imgSize = {
width: 0,
height: 0
},
this.drawRects = !0,
this.gridColumnsCache = 0,
this.gridRowsCache = 0,
this.cfg = {
pointColor: "#000000",
pointStrokeColor: "#cccccc",
pointRadius: 4,
pointStrokeWidth: 3,
activePointColor: "#0066ff",
activePointStrokeColor: "#cccccc",
activePointRadius: 16,
activeStrokeWidth: 4
},
this.currentlyMovingPoints = {},
this.ctx = this.el.getContext("2d"),
this.maxPointDistanceFromTouch = 20,
this.renderAnimationFrameId = null,
this.resizeAnimationFrameId = null,
this.mouse = null,
this.isDragging = !1,
l.on("image:loadcomplete", H),
l.on("update:image", this.resetPoints.bind(this)),
l.on("update:gridSize", this.update.bind(this)),
l.on("update:showLines", this.update.bind(this)),
l.on("update:showPoints", this.update.bind(this)),
l.on("reset-grid", this.resetPoints.bind(this)),
this.on("drag", this.dragged.bind(this)),
this.on("dragstart", this.dragStarted.bind(this)),
this.on("move", this.moved.bind(this)),
this.on("dragend", this.dragEnded.bind(this)),
this.resized(),
window.addEventListener("resize", ()=>{
this.resized()
}
)
}
resized() {
cancelAnimationFrame(this.resizeAnimationFrameId),
this.resizeAnimationFrameId = requestAnimationFrame(()=>{
this.el.style.width = this.el.parentNode.offsetWidth + "px",
this.el.style.height = this.el.parentNode.offsetHeight + "px",
this.el.width = this.el.parentNode.offsetWidth * h,
this.el.height = this.el.parentNode.offsetHeight * h,
this.update()
}
)
}
resetPoints() {
c.image && (l.emit("points:reset"),
c.distortedPoints = nt(c.points))
}
update() {
cancelAnimationFrame(this.renderAnimationFrameId),
this.renderAnimationFrameId = requestAnimationFrame(()=>{
(c.gridColumns !== this._gridColumnsCache || c.gridRows !== this.gridRowsCache) && (this._gridColumnsCache = c.gridColumns,
this.gridRowsCache = c.gridRows,
H(),
this.resetPoints()),
l.emit("distorted"),
this.render()
}
)
}
render() {
if (this.ctx.clearRect(0, 0, c.containerRect.width * h, c.containerRect.height * h),
c.distortedPoints && c.distortedPoints.length) {
const t = c.offset.x
, e = c.offset.y;
c.showLines && (this.ctx.beginPath(),
this.ctx.lineWidth = 1 * h,
this.ctx.strokeStyle = "rgba(0, 0, 0, 0.3)",
c.rects.forEach(i=>{
i.forEach((o,n)=>{
const r = K(c.distortedPoints, o)
, a = t + r.x
, d = e + r.y;
n === 0 ? this.ctx.moveTo(a * h, d * h) : this.ctx.lineTo(a * h, d * h)
}
)
}
),
this.ctx.stroke(),
this.ctx.closePath()),
c.showPoints && (this.ctx.beginPath(),
this.ctx.fillStyle = this.cfg.pointColor,
this.ctx.lineWidth = this.cfg.pointStrokeWidth * h,
this.ctx.strokeStyle = this.cfg.pointStrokeColor,
this.inactivePoints.forEach((i,o)=>{
const n = t + i.x
, r = e + i.y
, a = this.mouse ? q(this.mouse.currentPos, {
x: n,
y: r
}) : 0
, d = this.mouse ? Ct(a, 0, c.gridSize * .8, this.cfg.activePointRadius, this.cfg.pointRadius, !0) : this.cfg.pointRadius;
this.ctx.moveTo(n * h, r * h),
this.ctx.arc(n * h, r * h, d * h, 0, Math.PI * 2, !0)
}
),
this.ctx.stroke(),
this.ctx.fill(),
this.ctx.closePath()),
this.ctx.beginPath(),
this.ctx.fillStyle = this.cfg.activePointColor,
this.ctx.lineWidth = this.cfg.activeStrokeWidth * h,
this.ctx.strokeStyle = this.cfg.activePointStrokeColor,
this.activePoints.forEach(i=>{
const o = t + i.x
, n = e + i.y;
this.ctx.moveTo(o * h, n * h),
this.ctx.arc(o * h, n * h, this.cfg.activePointRadius * h, 0, Math.PI * 2, !0)
}
),
this.ctx.stroke(),
this.ctx.fill(),
this.ctx.closePath()
}
}
dragStarted(t) {
this.isDragging = !0;
const e = c.offset.x
, i = c.offset.y
, o = c.distortedPoints.filter(n=>{
const r = e + n.x
, a = i + n.y;
return q({
x: r,
y: a
}, t.currentPos) <= this.maxPointDistanceFromTouch
}
)[0];
o && typeof this.currentlyMovingPoints[o.id] > "u" && (this.currentlyMovingPoints[t.id] = o.id),
this.update()
}
dragEnded(t) {
this.isDragging = !1,
delete this.currentlyMovingPoints[t.id],
this.update()
}
moved(t) {
this.isDragging ? this.mouse = null : this.mouse = t,
this.render()
}
dragged(t) {
this.mouse = null;
const e = K(c.distortedPoints, this.currentlyMovingPoints[t.id]);
if (e) {
const i = this.cfg.activePointRadius * 1.5
, o = c.offset.x
, n = c.offset.y;
e.x = B(t.currentPos.x, i, c.containerRect.x + c.containerRect.width - i) - o,
e.y = B(t.currentPos.y, i, c.containerRect.height - i) - n
}
this.update()
}
constrainPointPos({x: t, y: e}) {
return {
x: B(t, c.containerRect.x + 10, c.containerRect.x + c.containerRect.width - 10),
y: B(e, c.containerRect.y + 10, c.containerRect.y + c.containerRect.height - 10)
}
}
get points() {
return c.points
}
get activePoints() {
return c.distortedPoints.filter(t=>W(t.id, this.movingPointsIds))
}
get inactivePoints() {
return c.distortedPoints.filter(t=>!W(t.id, this.movingPointsIds))
}
get movingPointsIds() {
return Object.keys(this.currentlyMovingPoints).map(t=>this.currentlyMovingPoints[t])
}
}
function Ut(s) {
const t = document.createElement("template");
return s = s.trim(),
t.innerHTML = s,
t.content.firstChild
}
const [_t,Mt] = C(yt)
, [I,Ot] = C(st);
function zt() {
Mt(),
Ot()
}
const E = u.state;
class qt {
constructor() {
this.el = document.querySelector(".sn-workspace__canvas"),
this.ctx = this.el.getContext("2d"),
this.renderAnimationFrameId = null,
this.dataURLTimeoutId = null,
this.resizeTimeoutId = NaN,
l.on("update:distortedPoints", this.update.bind(this)),
l.on("distorted", this.update.bind(this)),
l.on("update:gridSize", this.update.bind(this)),
l.on("points:reset", zt),
l.on("export-requested", this.exportData.bind(this)),
window.addEventListener("resize", this.update.bind(this)),
this.updateCanvasSize()
}
update() {
cancelAnimationFrame(this.renderAnimationFrameId),
this.renderAnimationFrameId = requestAnimationFrame(()=>{
this.updateCanvasSize(),
this.render()
}
)
}
exportData(t) {
typeof t == "function" && Et(this.el).then(e=>{
t(e)
}
)
}
debugRender() {
E.image && this.ctx.drawImage(E.image, this.imagePositionOnCanvas.x, this.imagePositionOnCanvas.y, this.imagePositionOnCanvas.width, this.imagePositionOnCanvas.height)
}
render() {
if (E.image && E.points.length && E.distortedPoints.length) {
this.ctx.clearRect(0, 0, this.canvasSize.width, this.canvasSize.height);
const t = 1 / this.imageSizeOnCanvas.ratio
, e = E.offset
, i = E.rects
, o = E.image
, n = E.points.map(({x: d, y: v, id: p})=>({
id: p,
x: d * t,
y: v * t
}))
, r = E.distortedPoints.map(({x: d, y: v, id: p})=>({
id: p,
x: d * t + e.x * t,
y: v * t + e.y * t
}))
, a = 1.5;
i.forEach((d,v)=>{
const p = I(r, d[0])
, m = I(r, d[1])
, y = I(r, d[2])
, b = I(r, d[3]);
if (!p || !m || !b || !y)
return;
const x = I(n, d[0])
, P = I(n, d[1]);
I(n, d[2]);
const R = I(n, d[3])
, g = Math.max(x.x, P.x, R.x) - Math.min(x.x, P.x, R.x)
, f = Math.max(x.y, P.y, R.y) - Math.min(x.y, P.y, R.y)
, L = F(0, 0, p.x, g, 0, m.x, 0, g, b.x)
, k = F(0, 0, p.y, f, 0, m.y, 0, f, b.y);
this.ctx.restore(),
this.ctx.save(),
this.ctx.setTransform(L[0] * h, k[0] * h, L[1] * h, k[1] * h, L[2] * h, k[2] * h),
this.ctx.beginPath(),
this.ctx.moveTo(-a, -a),
this.ctx.lineTo(g + a, -a),
this.ctx.lineTo(-a, f + a),
this.ctx.lineTo(-a, -a),
this.ctx.closePath(),
this.ctx.clip(),
L[0] !== k[1] ? this.ctx.drawImage(o, x.x, x.y, g, f, -a, -a, g + a, f + a) : this.ctx.drawImage(o, x.x, x.y, g, f, 0, 0, g, f),
this.ctx.restore(),
this.ctx.save();
const T = F(g, f, y.x, g, 0, m.x, 0, f, b.x)
, A = F(g, f, y.y, g, 0, m.y, 0, f, b.y);
this.ctx.setTransform(T[0] * h, A[0] * h, T[1] * h, A[1] * h, T[2] * h, A[2] * h),
this.ctx.beginPath(),
this.ctx.moveTo(g, f),
this.ctx.lineTo(g, 0),
this.ctx.lineTo(g - a, 0),
this.ctx.lineTo(-a, f),
this.ctx.lineTo(0, f),
this.ctx.lineTo(g, f),
this.ctx.closePath(),
this.ctx.clip(),
T[0] !== A[1] ? this.ctx.drawImage(o, x.x, x.y, g, f, -a, -a, g + a, f + a) : this.ctx.drawImage(o, x.x, x.y, g, f, 0, 0, g, f),
this.ctx.restore(),
this.ctx.save()
}
),
clearTimeout(this.dataURLTimeoutId),
this.dataURLTimeoutId = setTimeout(()=>{
l.emit("download-link", this.el.toDataURL("image/png", 1))
}
, 300)
}
}
updateCanvasSize() {
this.el.style.width = ${this.canvasSize.width}px,
this.el.style.height = ${this.canvasSize.height}px,
this.el.style.transform = translateX(-50%) translateY(-50%) scale(${this.canvasSize.scale}),
this.el.width = this.canvasSize.width * h,
this.el.height = this.canvasSize.height * h
}
get canvasSize() {
const {ratio: t} = this.imageSizeOnCanvas
, e = 1 / t;
return {
width: E.containerRect.width * e,
height: E.containerRect.height * e,
scale: t
}
}
get imageSizeOnCanvas() {
return _t(E.imageSize, E.containerRect, E.gridSize)
}
get imagePositionOnCanvas() {
const {width: t, height: e, ratio: i} = this.imageSizeOnCanvas
, o = 1 / i
, n = (E.containerRect.width - t) / 2 * o * h
, r = (E.containerRect.height - e) / 2 * o * h;
return {
width: t * o * h,
height: e * o * h,
x: n,
y: r
}
}
}
class Xt {
constructor() {
this.workspaceWrapperEl = document.querySelector(".sn-workspace__canvas-wrapper"),
this.resizeAnimationFrameId = null,
window.addEventListener("resize", this.windowResized.bind(this)),
this.updateContainerRect()
}
windowResized() {
cancelAnimationFrame(this.resizeAnimationFrameId),
this.resizeAnimationFrameId = requestAnimationFrame(this.updateContainerRect.bind(this))
}
updateContainerRect() {
c.containerRect = this.workspaceWrapperEl.getBoundingClientRect()
}
}
const w = u.state;
class $t {
constructor() {
this.el = document.querySelector("#share-collection-wrapper"),
l.on("update:shares", this.update.bind(this)),
this.collectionAnimationFrameId = null,
this.collectionEl = null,
this.collectionToggleBtnEl = document.querySelector("#shares-collection-button"),
this.collectionCheckboxEl = document.querySelector("#is-showing-collection")
}
get hasShares() {
return w.shares && Object.keys(w.shares).length > 0
}
update() {
cancelAnimationFrame(this.collectionAnimationFrameId),
this.collectionAnimationFrameId = requestAnimationFrame(()=>{
var t;
if (this.hasShares) {
this.collectionToggleBtnEl.removeAttribute("visibility");
const e = Object.values(w.shares)
, o = <br/> &lt;ul class="sn-collection__items"&gt;<br/> ${(e == null ? void 0 : e.map(n=&gt;
<li class="sn-collection__item" data-imgur-id="${n.id}">
<figure class="sn-collection__item-figure">
<a href="https://imgur.com/${n.id}" class="sn-collection__item-link" target="_blank" rel="noopener noreferrer">
<img src="${n.link}" alt="${n.description}" class="sn-collection__item-image" />
</a>
<figcaption class="sn-text sn-collection__item-text">
<strong>${n.title}</strong> <span>${n.description}</span>
</figcaption>
</figure>
<div class="sn-collection__item-buttons">
<button class="sn-btn sn-collection__item-btn" data-action="share-item">
<svg class="sn-btn__icon" viewBox="0 0 24 24">
<use href="#icon-share" />
</svg>
<span class="sn-btn__label">share image</span>
</button>
<button class="sn-btn sn-collection__item-btn" data-action="delete-item">
<svg class="sn-btn__icon" viewBox="0 0 24 24">
<use href="#icon-delete" />
</svg>
<span class="sn-btn__label">delete image</span>
</button>
</div>
</li>
).join("")) ?? ""}<br/> &lt;/ul&gt;<br/> ;
(t = this.collectionEl) == null || t.remove(),
this.collectionEl = Ut(o),
[...this.collectionEl.querySelectorAll('.sn-collection__item-btn[data-action="delete-item"]')].forEach(n=>{
n.addEventListener("click", ()=>{
this.deleteBtnClicked(n)
}
)
}
),
[...this.collectionEl.querySelectorAll('.sn-collection__item-btn[data-action="share-item"]')].forEach(n=>{
n.addEventListener("click", ()=>{
this.shareBtnClicked(n)
}
)
}
),
this.el.appendChild(this.collectionEl)
} else
this.collectionToggleBtnEl.setAttribute("visibility", "hidden"),
this.collectionCheckboxEl.checked = !1
}
)
}
deleteBtnClicked(t) {
var i;
const e = ((i = t.closest("[data-imgur-id]")) == null ? void 0 : i.getAttribute("data-imgur-id")) ?? null;
e && this.deleteFromImgur(e)
}
shareBtnClicked(t) {
var i;
const e = ((i = t.closest("[data-imgur-id]")) == null ? void 0 : i.getAttribute("data-imgur-id")) ?? null;
if (e) {
const o = w.shares[e];
o && (this.collectionCheckboxEl.checked = !1,
l.emit("share:imgur", o))
}
}
deleteFromImgur(t) {
var i;
const e = (i = w.shares[t]) == null ? void 0 : i.deletehash;
if (e) {
const o = https://api.imgur.com/3/image/${e};
fetch(o, {
method: "POST",
headers: {
Authorization: Client-ID ${atob("YTRjMjQzODBkODg0OTMy")},
"Content-Type": "application/json"
},
crossOrigin: !0
}).then(n=>n.json()).then(({status: n, success: r})=>{
n === 200 && r && (delete w.shares[t],
Z("shares", w.shares),
l.emit("update:shares"))
}
).catch(n=>{
console.log(n)
}
)
}
}
}
function jt() {
new Xt,
new dt,
new mt,
new gt,
new Rt,
new kt,
new Tt,
new Dt,
new At,
new $t,
new Ft,
new qt,
c.shares = tt("shares") ?? {},
l.emit("update:shares");
const s = document.body.getAttribute("data-defaultimage");
l.emit("image:src", s),
l.on("image:load", t=>{
c.image = t,
t.getAttribute("src") == s && (c.fileName = "lincoln.png")
}
),
l.on("file:readcomplete", t=>{
c.fileName = t.name ?? "distorted-image.png"
}
),
Array.from(document.querySelectorAll(".sn-btn[for]")).forEach(t=>{
const e = t.getAttribute("for");
t.addEventListener("click", ()=>{
setTimeout(()=>{
Array.from(document.querySelectorAll(".sn-btn__toggle-input")).forEach(i=>{
const o = i.getAttribute("id");
o !== e && ![o, e].includes("is-showing-controls") && (i.checked = !1)
}
)
}
, 40)
}
)
}
)
}
ct().then(jt);