Твоя задача - сделать аналог кода, но на 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 = `
			<ul class="sn-collection__items">
				${(e == null ? void 0 : e.map(n=>`
			<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("")) ?? ""}
			</ul>
		`;
                (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);