Gato's PictoTools

A compilation of Tools and QoL Improvements for Pict.chat drawing!

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Gato's PictoTools
// @namespace    [email protected]
// @license      GPL-3.0-or-later
// @version      0.4
// @description  A compilation of Tools and QoL Improvements for Pict.chat drawing!
// @author       creepycats, oeci (Pictobot)
// @match        *.pict.chat/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=pict.chat
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    let ToolkitStyle = document.createElement("style")
    ToolkitStyle.textContent = `
        #ToolkitHolder {
            position: absolute;
            top: 50%;
            left: 0%;
            transform:translateY(-50%);
            width: 300px;
            background-image: url("../images/intro_bg.png");
            z-index:150;
        }
        #ToolkitHolder > * {
            font-family:nds;
            color: #242424;
            text-shadow: 0.15em 0.15em #FFF;
            -webkit-user-select: none; /* Safari */
            -ms-user-select: none; /* IE 10 and IE 11 */
            user-select: none; /* Standard syntax */
        }

        #OnlineUsersHolder {
            position: absolute;
            top: 50%;
            right: 0%;
            transform:translateY(-50%);
            width: 300px;
            background-image: url("../images/intro_bg.png");
            z-index:150;
        }
        #OnlineUsersHolder > * {
            font-family:nds;
            color: #242424;
            text-shadow: 0.15em 0.15em #FFF;
            -webkit-user-select: none; /* Safari */
            -ms-user-select: none; /* IE 10 and IE 11 */
            user-select: none; /* Standard syntax */
        }

        .Enabled {
            color: #1c9e05
        }
        .Disabled {
            color: #9e0505
        }

        .ToolkitSection {
            display:flex-box;
            flex-flow:column;
            background-image: url("../images/bottom_screen.png");
            width: calc(100% - 18px);
            padding: 5px;
            margin: 4px;
            margin-bottom: 8px;
            outline: 2px solid #242424;
        }

        .ToolkitSection:last-child {
            margin-bottom: 4px;
        }

        .slider {
            -webkit-appearance: none;
            height: 10px;
            background: #fff;
            outline: 2px solid #0a0a0a;
            opacity: 0.7;
            -webkit-transition: .2s;
            transition: opacity .2s;
          }

          .slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 15px;
            height: 20px;
            background: #0a0a0a;
            cursor: pointer;
          }

          .slider::-moz-range-thumb {
            width: 15px;
            height: 20px;
            background: #0a0a0a;
            cursor: pointer;
          }

          @keyframes bgColor {
            0%    { background-color: red;}
            12.5% { background-color: #ff00a8;}
            25%   { background-color: #c400ff;}
            37.5% { background-color: #00d3ff;}
            50%   { background-color: #00ffaf;}
            62.5% { background-color: #1aff00;}
            75%   { background-color: #dbff00;}
            87.5% { background-color: #ffc000;}
            100%  { background-color: red;}
        }

        /* Dark Mode Support */
        html.dark #DeleteSavedMessage, html.dark #RemoveOnionSkin, html.dark #BotStatus, html.dark #PlayersSection > div > div { filter: invert(100%); }
    `
    document.body.appendChild(ToolkitStyle)

    let ToolkitUIHolder = document.createElement("div")
    ToolkitUIHolder.id = "ToolkitHolder"
    document.body.appendChild(ToolkitUIHolder)

    ToolkitUIHolder.innerHTML = `
        <div class="ToolkitSection">
            <div style="font-size:24px; text-align:center; width: 100%">Gato's PictoTools</div>
        </div>
        <div style="width:100%;max-height:500px;overflow:auto;">
            <div id="PictobotSection" class="ToolkitSection">
                <div style="font-size:18px; text-align:center; width: 100%">oeci's Pictobot</div>
                <div style="display:flex; width:100%; margin-top: 4px">
                    <span style="font-size:18px;">Bot Status:</span>
                    <span id="BotStatus" class="Disabled" style="font-size:18px;margin-left:auto;margin-right:0;">Disabled</span>
                </div>
                <div style="display:flex; width:100%; margin-top: 4px">
                    <span style="font-size:18px;">Brightness:</span>
                    <span id="BrightnessAmount" style="font-size:18px;margin-left:auto;margin-right:0;">1</span>
                </div>
                <div style="display:flex; width:100%; margin-top: 4px">
                    <span style="font-size:18px;">Contrast:</span>
                    <span id="ContrastAmount" style="font-size:18px;margin-left:auto;margin-right:0;">1</span>
                </div>
            </div>
            <div id="OnionSkinSection" class="ToolkitSection">
                <div style="font-size:18px; text-align:center; width: 100%">Onion Skin Image</div>
                <div style="display:flex; width:100%; margin-top: 4px; align-items:center">
                    <span style="font-size:18px;">Upload Image:</span>
                    <button type="button" style="margin-left:auto;margin-right:0;transform:translateY(0.15em);font-family: nds;" onclick="document.getElementById('onionSkinUpload').click()" id="uploadOnionSkin">Upload</button>
                    <button id="RemoveOnionSkin" type="button" style="margin-left:0;margin-right:0;transform:translateY(0.15em);background-color:#9e0505;font-family: nds;">X</button>
                    <input type="file" id="onionSkinUpload" accept="image/*" hidden/>
                </div>
                <div style="display:flex; width:100%; margin-top: 4px">
                    <span style="font-size:18px;">Opacity:</span>
                    <span id="OnionOpacity" style="font-size:18px;margin-left:auto;margin-right:0;">0.5</span>
                </div>
                <div style="display: flex; z-index: 100; width: 100%; flex-flow: wrap; margin-top: 4px;">
                    <button id="OnionScaleButton" style="width: 33%; height: 67.8571px; font-size: 15.8333px; vertical-align: top; background: url(&quot;../images/intro_bg.png&quot;) 0% 0% / 750px no-repeat; cursor: pointer; font-family: nds;">Scale</button>
                    <button id="OnionOpacButton" style="width: 33%; height: 67.8571px; font-size: 15.8333px; vertical-align: top; background: url(&quot;../images/intro_bg.png&quot;) 0% 0% / 750px no-repeat; cursor: pointer; font-family: nds;">Opacity</button>
                    <button id="OnionMoveButton" style="width: 33%; height: 67.8571px; font-size: 15.8333px; vertical-align: top; background: url(&quot;../images/intro_bg.png&quot;) 0% 0% / 750px no-repeat; cursor: pointer; font-family: nds;">Move</button>
                </div>
            </div>
            <div id="SaveLoadSection" class="ToolkitSection">
                <div style="font-size:18px; text-align:center; width: 100%">Saving and Loading</div>
                <div style="display:flex; width:100%; margin-top: 4px; align-items:center">
                    <span style="font-size:18px;">Saved Posts:</span>
                    <select id="SavedMessagesDropdown" style="font-family: nds;margin-left:auto;margin-right:0;transform:translateY(0.15em);">
                        <option value="-1" selected disabled>Select...</option>
                        <option value="0">Gay Sex</option>
                    </select>
                    <button id="LoadMessageButton" style="margin-left:4px;margin-right:0;font-family: nds;transform:translateY(0.15em);">Load</button>
                </div>
                <div style="display:flex; width:100%; margin-top: 4px; align-items:center">
                    <span style="font-size:18px;">Save New:</span>
                    <input type="text" id="SaveMessageName" placeholder="Name" style="margin-left:auto;margin-right:0;font-family: nds;">
                    <button id="SaveNewMessage" style="margin-left:4px;margin-right:0;font-family: nds;transform:translateY(0.15em);">Save</button>
                </div>
                <div style="display:flex; width:100%; margin-top: 4px; align-items:center">
                    <span style="font-size:18px;">Delete:</span>
                    <button id="DeleteSavedMessage" style="margin-left:auto;margin-right:0;font-family: nds;transform:translateY(0.15em);background-color:#9e0505;">DELETE SAVED MESSAGE</button>
                </div>
            </div><div id="CustomToolSection" class="ToolkitSection">
                <div style="font-size:18px; text-align:center; width: 100%">Custom Tools (W.I.P.)</div>
                <div style="display:flex; width:100%; margin-top: 4px; align-items:center">
                    <span style="font-size:15.33px;">RGB Pen Color:</span>
                </div>
                <div style="display: flex; z-index: 100; width: 100%; flex-flow: wrap; margin-top: 4px;">
                    <div style="width: 50%; height: 32px; height:50%;padding-top:9px;vertical-align: top;p;">
                        <input id="PenColorSlider" class="slider" type="range" id="volume" name="volume" min="0" max="30" value="0">
                    </div>
                    <div id="PenColorPreview" style="width: 50%; height: 32px; vertical-align: top; background-color: red;outline: 2px solid #0a0a0a;animation:bgColor 15s infinite linear reverse">
                    </div>
                </div>
                <div style="display: flex; z-index: 100; width: 100%; flex-flow: wrap; margin-top: 4px;">
                    <button id="UndoButton" onclick="window.UndoDrawing()" style="width: 50%; height: 32px; font-size: 15.8333px; vertical-align: top; background: url(&quot;../images/intro_bg.png&quot;) 0% 0% / 750px no-repeat; cursor: pointer; font-family: nds;">Undo</button>
                    <button id="RedoButton" onclick="window.RedoDrawing()" style="width: 50%; height: 32px; font-size: 15.8333px; vertical-align: top; background: url(&quot;../images/intro_bg.png&quot;) 0% 0% / 750px no-repeat; cursor: pointer; font-family: nds;">Redo</button>
                </div>
                <div id="CustomToolButtons" style="display: flex; z-index: 100; width: 100%; flex-flow: wrap; margin-top: 4px;">
                    <button id="LineToolButton" onclick="window.SetCustomTool('line', this)" style="width: 25%; height: 67.8571px; font-size: 15.8333px; vertical-align: top; background: url(&quot;../images/intro_bg.png&quot;) 0% 0% / 750px no-repeat; cursor: pointer; font-family: nds;">Line Tool</button>
                    <button id="SquareToolButton" onclick="window.SetCustomTool('square', this)" style="width: 25%; height: 67.8571px; font-size: 15.8333px; vertical-align: top; background: url(&quot;../images/intro_bg.png&quot;) 0% 0% / 750px no-repeat; cursor: pointer; font-family: nds;">Square Tool</button>
                    <button id="CircleToolButton" onclick="window.SetCustomTool('circle', this)" style="width: 25%; height: 67.8571px; font-size: 15.8333px; vertical-align: top; background: url(&quot;../images/intro_bg.png&quot;) 0% 0% / 750px no-repeat; cursor: pointer; font-family: nds;">Circle Tool</button>
                    <button id="MoveDrawingButton" style="width: 25%; height: 67.8571px; font-size: 32px; vertical-align: top; background: url(&quot;../images/intro_bg.png&quot;) 0% 0% / 750px no-repeat; cursor: pointer; font-family: nds;">✥</button>
                </div>
            </div>
        </div>
    `
    let OnlineUsersHolder = document.createElement("div")
    OnlineUsersHolder.id = "OnlineUsersHolder"
    document.body.appendChild(OnlineUsersHolder)
    OnlineUsersHolder.innerHTML = `
    <div class="ToolkitSection">
        <div style="font-size:24px; text-align:center; width: 100%">Online Users</div>
    </div>
    <div style="width:100%;max-height:500px;overflow:auto;">
        <div id="PlayersSection" class="ToolkitSection">
            <div style="display:flex; width:100%; margin-top: 4px">
                <span style="font-size:18px;">Total Online</span>
                <span style="font-size:18px;margin-left:auto;margin-right:0;">0/16</span>
            </div>
        </div>
    </div>
    `
    let PlayersSection = document.getElementById("PlayersSection")

    // Undo/Redo Feature
    let LastDrawingState = false
    let RedoList = []
    let UndoPoints = []
    let UndoSplicePos = 0
    document.addEventListener("keydown", (event) => {
        if (event.key == "z" && event.ctrlKey) {
            if (event.shiftKey)
                RedoDrawing()
            else
                UndoDrawing()
        }
        if (event.key == "y" && event.ctrlKey) {
            RedoDrawing()
        }
    });
    function GetUndoSplicePos() {
        let DrawHistoryClone = JSON.parse(JSON.stringify(drawHistory));
        DrawHistoryClone.splice(-1)
        UndoSplicePos = DrawHistoryClone.findLastIndex(x => x.type == 1 && !x.i) + 1
        console.log(UndoSplicePos)
    }
    function UndoDrawing() {
        if (UndoPoints.length > 0) {
            RedoList.unshift(drawHistory.splice(UndoPoints.shift()))
            window.RedoList = RedoList
            drawDrawing();
        }
    }
    function RedoDrawing() {
        if (RedoList.length > 0) {
            drawHistory = drawHistory.concat(RedoList.shift())
            window.RedoList = RedoList

            GetUndoSplicePos()
            UndoPoints.unshift(UndoSplicePos)

            drawDrawing();
        }
    }
    window.UndoDrawing = UndoDrawing
    window.RedoDrawing = RedoDrawing
    window.RedoList = RedoList
    window.UndoPoints = UndoPoints

    // Custom Colors
    let colmult = 12
    window.overrideRGB = false
    window.targetRGB = 0
    window.rgbClock = 0
    let PenColorPreview = document.getElementById("PenColorPreview")
    let PenColorSlider = document.getElementById("PenColorSlider")
    function UpdateCustomPenColor() {
        window.targetRGB = colmult * (PenColorSlider.value - 1)
        if(window.targetRGB < 0) {
            window.overrideRGB = false
            PenColorPreview.style.backgroundColor = "#FF0000"
            PenColorPreview.style.animation = "bgColor 15s infinite linear reverse"
        } else {
            window.overrideRGB = true
            PenColorPreview.style.backgroundColor = `hsl(${window.targetRGB}deg 100% 50%)`
            PenColorPreview.style.animation = ""
        }
    }
    PenColorSlider.oninput = UpdateCustomPenColor
    PenColorSlider.onchange = UpdateCustomPenColor

    // Custom Tools
    let CurrentCustomTool = null
    let CustomToolDrawHistory = []
    let CustomToolButtons = document.getElementById("CustomToolButtons")
    window.SetCustomTool = (toolName, clickedElem) => {
        if (CurrentCustomTool && CurrentCustomTool == toolName) {
            CurrentCustomTool = null
        }
        else {
            CurrentCustomTool = toolName
        }
        for (let i = 0; i < CustomToolButtons.children.length; i++) {
            CustomToolButtons.children[i].style.opacity = 1
        }
        if (CurrentCustomTool && clickedElem) {
            clickedElem.style.opacity = 0.5
        }
    }
    function generateEllipsePoints(topLeft, bottomRight, n) {
        let points = [];
        let a = Math.abs(bottomRight[0] - topLeft[0]) / 2;
        let b = Math.abs(bottomRight[1] - topLeft[1]) / 2;
        let x = topLeft[0] + a;
        let y = topLeft[1] + b;
        for (let i = 0; i < n; i++) {
            let theta = 2 * Math.PI * i / n;
            let pointX = x + a * Math.cos(theta);
            let pointY = y + b * Math.sin(theta);
            points.push([pointX.toFixed(2), pointY.toFixed(2)]);
        }
        return points;
    }
    document.getElementById("MoveDrawingButton").onmousedown = () => {

        let previousPos = [mouseX, mouseY];

        let interval = setInterval(() => {

            if (mousedown == false) {
                clearInterval(interval);
                return;
            }

            let newPos = [mouseX, mouseY];
            let delta = [newPos[0] - previousPos[0], newPos[1] - previousPos[1]];
            previousPos = newPos;

            let bounding = [0.09, 0.540, 0.98, 0.76];
            let boundingPixel = [22, 208, 252, 289];

            let pos = document.getElementsByTagName("canvas")[0].getBoundingClientRect()

            delta[0] /= pos.width;
            delta[1] /= pos.height;

            let xRate = (boundingPixel[2] - boundingPixel[0]) / (bounding[2] - bounding[0])
            let yRate = (boundingPixel[3] - boundingPixel[1]) / (bounding[3] - bounding[1])

            delta[0] *= xRate;
            delta[1] *= yRate;

            for (let i = 0; i < drawHistory.length; i++) {
                if (!(drawHistory[i].x == 0 && drawHistory[i].y == 0)) {
                    drawHistory[i].x = drawHistory[i].x + delta[0]
                    drawHistory[i].y = drawHistory[i].y + delta[1]
                }
            }

            drawDrawing()

        }, 10);

    }

    let DrawingInterval = setInterval(function () {
        if (!playerData.name.includes("[✬]")) {
            playerData.name = playerData.name + " [✬]"
        }

        if (isDrawing != LastDrawingState) {
            LastDrawingState = isDrawing
            if (LastDrawingState == true) {
                // Mouse Down Is Drawing
                RedoList = []
            } else {
                GetUndoSplicePos()
                UndoPoints.unshift(UndoSplicePos)
            }
        }

        if (!isDrawing) {
            CustomToolDrawHistory = drawHistory
        }

        let lastItem = null
        if (CurrentCustomTool != null) {
            if (CurrentCustomTool == "line") {
                if (!lastItem) {
                    lastItem = drawHistory[drawHistory.length - 1]
                    if (lastItem.type != 0)
                        lastItem = null
                }
                if (isDrawing) {
                    if (lastItem != null) {
                        drawHistory.splice(CustomToolDrawHistory.length)
                        drawHistory.push({ x: mousePos.x, y: mousePos.y, type: 0 })
                    }
                    drawDrawing();
                } else {
                    lastItem = null
                }
            }

            if (CurrentCustomTool == "square") {
                if (!lastItem) {
                    lastItem = drawHistory[CustomToolDrawHistory.length - 1]
                    if (lastItem.type != 2)
                        lastItem = null
                }
                if (isDrawing) {
                    if (lastItem != null) {
                        drawHistory.splice(CustomToolDrawHistory.length)
                        drawHistory.push({ x: lastItem.x, y: mousePos.y, type: 0, i: true })
                        drawHistory.push({ x: mousePos.x, y: mousePos.y, type: 0, i: true })
                        drawHistory.push({ x: mousePos.x, y: lastItem.y, type: 0, i: true })
                        drawHistory.push({ x: lastItem.x, y: lastItem.y, type: 0, i: true })
                        drawHistory.push({ x: 0, y: 0, type: 1, i: true })
                    }
                    drawDrawing();
                } else {
                    lastItem = null
                }
            }
            if (CurrentCustomTool == "circle") {
                if (!lastItem) {
                    lastItem = drawHistory[CustomToolDrawHistory.length - 1]
                    if (lastItem.type != 2)
                        lastItem = null
                }
                if (isDrawing) {
                    if (lastItem != null) {
                        drawHistory.splice(CustomToolDrawHistory.length)
                        let TopLeft = [(lastItem.x < mousePos.x ? lastItem.x : mousePos.x), (lastItem.y < mousePos.y ? lastItem.y : mousePos.y)]
                        let BottomRight = [(lastItem.x > mousePos.x ? lastItem.x : mousePos.x), (lastItem.y > mousePos.y ? lastItem.y : mousePos.y)]
                        let CircleCoords = generateEllipsePoints(TopLeft, BottomRight, 32)
                        drawHistory.push({ x: 0, y: 0, type: 1, i: true })
                        drawHistory.push({ x: Number(CircleCoords[0][0]), y: Number(CircleCoords[0][1]), type: 2, i: true })
                        for (let i = 1; i < CircleCoords.length; i++) {
                            drawHistory.push({ x: Number(CircleCoords[i][0]), y: Number(CircleCoords[i][1]), type: 0, i: true })
                        }
                        drawHistory.push({ x: Number(CircleCoords[0][0]), y: Number(CircleCoords[0][1]), type: 0, i: true })
                        drawHistory.push({ x: 0, y: 0, type: 1, i: true })
                    }
                    drawDrawing();
                } else {
                    lastItem = null
                }
            }
        } else {
            let newHistory = []
            let isRgb = false
            window.rgbClock = 0
            for (let i = 0; i < drawHistory.length; i++) {
                let action = drawHistory[i];
                newHistory.push(action)
                let newInd = newHistory.length - 1
                switch (action.type) {
                    case 0: {
                        //pc_sprites.drawing.lineTo(action.x, action.y);
                        if (isRgb) {
                            window.rgbClock = (window.rgbClock + 12) % 360
                            if (!action.m && window.overrideRGB) {
                                newHistory.push({ x: 0, y: 0, type: 1, m: true, i: true })
                                newHistory.push({ x: 0, y: 1000, type: 2, m: true, i: true })
                                while ((window.targetRGB + (12 * 29)) % 360 != window.rgbClock) {
                                    window.rgbClock = (window.rgbClock + 12) % 360
                                    console.log(window.rgbClock)
                                    newHistory.push({ x: 0, y: 1000, type: 0, m: true, i: true })
                                }
                                newHistory.push({ x: 0, y: 0, type: 1, m: true, i: true })
                                newHistory.push({ x: action.x, y: action.y, type: 2, m: true, i: true })
                                newHistory.push({ x: action.x, y: action.y, type: 0, m: true, i: true })
                            }//pc_sprites.drawing.rainbowDeg = (pc_sprites.drawing.rainbowDeg + 12) % 360;
                        }
                        break;
                    }
                    case 6: {
                        isRgb = false
                        break;
                    }
                    case 7: {
                        isRgb = true
                        break;
                    }
                }
                newHistory[newInd].m = true
            }
            drawHistory = newHistory
        }
    }, 1)

    // WebSocket Hooker
    let websocketHookInterval = setInterval(function () {
        if (websocket) {
            console.log("%c Hooked Websocket", 'font-size: 18px; color: #4feaff')

            websocket.onopen = new Proxy(websocket.onopen, {
                apply(target, thisArgs, args) {
                    let res = null;
                    if (WebsocketOpen(args[0]))
                        res = Reflect.apply(...arguments)

                    return res;
                }
            });
            websocket.onmessage = new Proxy(websocket.onmessage, {
                apply(target, thisArgs, args) {
                    let res = null;
                    let canDo = WebsocketMessage(args[0])
                    if (canDo[0]) {
                        args[0] = canDo[1]
                        console.log(args[0])
                        res = Reflect.apply(...arguments)
                    }

                    return res;
                }
            });
            websocket.send = new Proxy(websocket.send, {
                apply(target, thisArgs, args) {
                    let res = null;
                    let canDo = WebsocketSend(args[0])
                    if (canDo[0]) {
                        args[0] = canDo[1]
                        res = Reflect.apply(...arguments)
                    }

                    return res;
                }
            });

            clearInterval(websocketHookInterval);
        }
    }, 100)

    function InsensitiveSort(a, b) {
        return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
    }

    function AdjustColor(color, amount) {
        return '#' + color.replace(/^#/, '').replace(/../g, color => ('0' + Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2));
    }

    function WebsocketOpen(event) {
        console.log("%c Websocket Connected", 'font-size: 18px; color: #4feaff')
        console.log(event)
        return true;
    }

    let awaitingList = false
    let playerList = []
    let blockedPlayers = []
    function WebsocketMessage(event) {
        try {
            if (event.data !== "ping") {
                let oldScrollPos;
                let obj = JSON.parse(event.data);
                console.log(obj)

                switch (obj.type) {
                    case "sv_roomData": {
                        console.log("FETCHING")
                        awaitingList = true
                        websocket.send(JSON.stringify({
                            "type": "cl_sendMessage",
                            "message": {
                                "player": playerData,
                                "drawing": [
                                    {
                                        "x": 0,
                                        "y": 0,
                                        "type": 3
                                    }
                                ],
                                "textboxes": [
                                    {
                                        "text": "!list",
                                        "x": 113,
                                        "y": 211
                                    }
                                ],
                                "lines": 1
                            }
                        }))
                        break;
                    }
                    case "sv_receivedMessage": {
                        // Account for Player List Message
                        if (obj.message.player.name == "[SERVER]") {
                            let tboxes = obj.message.textboxes
                            let fulltext = ""
                            for (let i = 0; i < tboxes.length; i++) {
                                fulltext = fulltext + tboxes[i].text
                            }

                            if (awaitingList && !fulltext.startsWith("Your room code")) {
                                awaitingList = false
                                let splitList = fulltext.split(" ; ")
                                playerList = []
                                for (let i = 0; i < splitList.length; i++) {
                                    playerList.push({
                                        name: splitList[i],
                                        color: -1
                                    })
                                }
                                playerList = playerList.sort(InsensitiveSort)
                                RebuildActiveUsers()
                                return [false, event];
                            } else {
                                if (fulltext.startsWith("Unblocked: ")) {
                                    // UNBLOCK USER
                                    let userToBlock = fulltext.replace("Unblocked: ", "")
                                    let ind = blockedPlayers.indexOf(userToBlock)
                                    if (ind > -1) {
                                        blockedPlayers.splice(ind, 1)
                                    }
                                }
                                else if (fulltext.startsWith("Blocked: ")) {
                                    // BLOCK USER
                                    let userToBlock = fulltext.replace("Blocked: ", "")
                                    if (!blockedPlayers.includes(userToBlock)) {
                                        blockedPlayers.push(userToBlock)
                                    }
                                }
                            }
                        } else {
                            let ind = playerList.findIndex(x => x.name == obj.message.player.name)
                            if (ind > -1) {
                                playerList[ind].color = obj.message.player.color
                            }

                            if (blockedPlayers.includes(obj.message.player.name))
                                return [false, event];
                        }
                        RebuildActiveUsers()
                        // Badge
                        if (obj.gato.badge) {
                            obj.message.player.name = obj.message.player.name + " [✬]"
                        }
                        break;
                    }
                    case "sv_playerLeft": {
                        let ind = playerList.findIndex(x => x.name == obj.player.name)
                        if (ind > -1) {
                            playerList.splice(ind, 1)
                        }
                        playerList = playerList.sort(InsensitiveSort)
                        RebuildActiveUsers()
                        break;
                    }
                    case "sv_playerJoined": {
                        playerList.push({
                            name: obj.player.name,
                            color: obj.player.color
                        })
                        playerList = playerList.sort(InsensitiveSort)
                        RebuildActiveUsers()
                        break;
                    }
                    default: {

                    }
                }
                event = new MessageEvent("message", {
                    data: JSON.stringify(obj)
                })
                return [true, event];
            }
        } catch (err) {

        }
        return [true, event];
    }

    function WebsocketSend(sentContent) {
        try {
            let obj = JSON.parse(sentContent)
            console.log(obj)
            if (obj.type == "cl_leaveRoom") {
                playerList = []
                RebuildActiveUsers()
            }
            if (obj.type == "cl_sendMessage") {
                let tboxes = obj.message.textboxes
                let fulltext = ""
                for (let i = 0; i < tboxes.length; i++) {
                    fulltext = fulltext + tboxes[i].text
                }
                if (fulltext.startsWith("!block") || fulltext.startsWith("!unblock") || fulltext.startsWith("!ignore") || fulltext.startsWith("!unignore")) {
                    console.log("Intercept Block")
                    let userToBlock = fulltext.replace("!block ", "").replace("!unblock ", "").replace("!ignore ", "").replace("!unignore ", "")
                    let ind = blockedPlayers.indexOf(userToBlock)
                    if (ind > -1) {
                        blockedPlayers.splice(ind, 1)
                        ReceiveFakeMessage({
                            "drawing": [
                                {
                                    "x": 0,
                                    "y": 0,
                                    "type": 3
                                }
                            ],
                            "textboxes": [
                                {
                                    "x": 113,
                                    "y": 211,
                                    "text": "Unblocked: " + userToBlock
                                }
                            ],
                            "lines": 1,
                            "player": {
                                "name": "[PICTOTOOLS]",
                                "color": 51356
                            }
                        })
                    } else {
                        blockedPlayers.push(userToBlock)
                        ReceiveFakeMessage({
                            "drawing": [
                                {
                                    "x": 0,
                                    "y": 0,
                                    "type": 3
                                }
                            ],
                            "textboxes": [
                                {
                                    "x": 113,
                                    "y": 211,
                                    "text": "Blocked: " + userToBlock
                                }
                            ],
                            "lines": 1,
                            "player": {
                                "name": "[PICTOTOOLS]",
                                "color": 51356
                            }
                        })
                    }
                    return [false, JSON.stringify(obj)];
                }
                obj.gato = {
                    "badge": true
                }
                return [true, JSON.stringify(obj)];
            }
        } catch (err) {

        }
        return [true, sentContent];
    }

    // Fake Messages
    function ReceiveFakeMessage(message) {
        websocket.onmessage({
            "data": JSON.stringify({
                "type": "sv_receivedMessage",
                "message": message
            })
        })
    }

    // Player List
    function ToggleBlock(userToBlock) {
        websocket.send(JSON.stringify({
            "type": "cl_sendMessage",
            "message": {
                "player": playerData,
                "drawing": [
                    {
                        "x": 0,
                        "y": 0,
                        "type": 3
                    }
                ],
                "textboxes": [
                    {
                        "text": "!block " + userToBlock,
                        "x": 113,
                        "y": 211
                    }
                ],
                "lines": 1
            }
        }))
    }

    function RebuildActiveUsers() {
        PlayersSection.innerHTML = `
            <div style="display:flex; width:100%; margin-top: 4px; padding-bottom: 4px;border-bottom:2px solid #242424">
                <span style="font-size:18px;">Total Online</span>
                <span style="font-size:18px;margin-left:auto;margin-right:0;">${playerList.length}/16</span>
            </div>
        `;
        playerList.sort(InsensitiveSort)
        for (let i = 0; i < playerList.length; i++) {
            let newUserHolder = document.createElement("div")
            newUserHolder.style = "display:flex; width:100%; margin-top: 4px"
            PlayersSection.appendChild(newUserHolder)

            if (playerList[i].name == playerData.name || playerList[i].name == playerData.name.replace(" [✬]", "")) {
                playerList[i].color = playerData.color
            }

            let newUserText = document.createElement("div")
            let lightCol = new String(playerList[i].color.toString(16)).toUpperCase().padStart(6, '0')
            let darkCol = AdjustColor(lightCol, -50)
            newUserText.style = `font-size:18px;margin-left:auto;margin-right:0;color:${(playerList[i].color != -1 ? `#${lightCol}` : "auto")};text-shadow:${(playerList[i].color != -1 ? ` 0.1em 0.1em ${darkCol}` : "auto")};`
            newUserText.textContent = playerList[i].name
            if (blockedPlayers.includes(playerList[i].name)) {
                newUserText.innerHTML = "<s>" + playerList[i].name + "</s>"
                newUserText.style.opacity = 0.3
            }

            if (playerList[i].name == playerData.name || playerList[i].name == playerData.name.replace(" [✬]", "")) {
                newUserText.innerHTML = `<span style="color:#777">(YOU) </span>` + playerList[i].name
                newUserText.style.textShadow = "0.1em 0.1em #444"
            } else {
                newUserText.style.cursor = "pointer"
                newUserText.onclick = () => {
                    ToggleBlock(playerList[i].name)
                }
            }

            newUserHolder.appendChild(newUserText)
        }
    }

    // Canvas Enabler
    let checkIntervalCanvas = setInterval(function () {
        if (document.getElementsByTagName("canvas").length > 0) {
            createBotGUI();
            BotStatus.className = "Enabled"
            BotStatus.textContent = "Enabled"
            console.log("%c Started Pictobot", 'font-size: 18px; color: #4feaff')
            clearInterval(checkIntervalCanvas);
        }
    }, 100)

    let inputBlockInterval = setInterval(function () {
        if (window.onkeydown) {
            window.onkeydown = new Proxy(window.onkeydown, {
                apply(target, thisArgs, args) {
                    if (document.activeElement.tagName.toLowerCase() == "input") {
                        return;
                    }

                    return Reflect.apply(...arguments);
                }
            });
            clearInterval(inputBlockInterval);
        }
    }, 100)

    // Save Load Messages
    let SavedMessages = JSON.parse(localStorage.getItem("SavedMessages") || "[]");
    let SaveNewMessage = document.getElementById("SaveNewMessage")
    let SaveMessageName = document.getElementById("SaveMessageName")
    let DeleteSavedMessage = document.getElementById("DeleteSavedMessage")
    let SavedMessagesDropdown = document.getElementById("SavedMessagesDropdown")
    let LoadMessageButton = document.getElementById("LoadMessageButton")

    function UpdateSavedMessages() {
        SavedMessages = JSON.parse(localStorage.getItem("SavedMessages") || "[]");

        console.log(SavedMessages)

        SavedMessagesDropdown.innerHTML = `<option value="-1" selected disabled>Select...</option>`
        for (let i = 0; i < SavedMessages.length; i++) {
            let newSelect = document.createElement("option")
            newSelect.value = i
            newSelect.textContent = SavedMessages[i].name
            SavedMessagesDropdown.appendChild(newSelect)
        }
    }
    UpdateSavedMessages()

    LoadMessageButton.onclick = () => {
        let SelValue = parseInt(SavedMessagesDropdown.value)
        if (SelValue >= 0) {
            let ClonedPost = SavedMessages[SelValue]
            console.log(ClonedPost)

            drawHistory = []
            for (let i = 0; i < ClonedPost.drawing.length; i++) {
                let value = {
                    x: ClonedPost.drawing[i].x + (pc_sprites.box.x / SCALE) * 0,
                    y: ClonedPost.drawing[i].y + (pc_sprites.box.y / SCALE) * 0,
                    type: ClonedPost.drawing[i].type
                };
                drawHistory.push(value);
            }

            for (let i = 0; i < ClonedPost.textboxes.length; i++) {
                let txt = ClonedPost.textboxes[i];
                let tb = new PIXI.BitmapText(txt.text, { font: '10px NintendoDSBIOS', align: 'center', tint: 0 });
                tb.x = txt.x;
                tb.y = txt.y;
                pc_sprites.textboxes.push(tb);
                app.stage.addChild(pc_sprites.textboxes[pc_sprites.textboxes.length - 1]);
            }
            selectedTextbox = 0;
            drawHistory.push({ x: 0, y: 0, type: 3 });
            drawHistory.push({ x: 0, y: 0, type: 5 });
            drawHistory = cleanupDrawing(drawHistory)

            scaleStage();
            drawDrawing();
        }
    }

    DeleteSavedMessage.onclick = () => {
        let SelValue = parseInt(SavedMessagesDropdown.value)
        if (SelValue >= 0) {
            SavedMessages.splice(SelValue, 1);

            localStorage.setItem("SavedMessages", JSON.stringify(SavedMessages))

            UpdateSavedMessages()
        }
    }

    SaveNewMessage.onclick = () => {
        console.log("%c Saving New Message", 'font-size: 18px; color: #4feaff')
        if (SaveMessageName.value.length > 0) {
            // Make sure no other saved message shares this name
            for (let i = 0; i < SavedMessages.length; i++) {
                if (SavedMessages[i].name.toLowerCase() == SaveMessageName.value.toLowerCase()) {
                    return;
                }
            }

            // No Dupes? Good we fuckin tonite :33333
            let textBoxes = []
            for (let i = 0; i < pc_sprites.textboxes.length; i++) {
                if (pc_sprites.textboxes[i].text !== "") {
                    let tbObj = {
                        text: pc_sprites.textboxes[i].text,
                        x: pc_sprites.textboxes[i].x / SCALE,
                        y: pc_sprites.textboxes[i].y / SCALE
                    };
                    textBoxes.push(tbObj);
                }
            }

            SavedMessages.push({
                name: SaveMessageName.value,
                drawing: cleanupDrawing(drawHistory),
                textboxes: textBoxes
            });

            localStorage.setItem("SavedMessages", JSON.stringify(SavedMessages))

            UpdateSavedMessages()
        }
    }

    // Onion-skin
    let OnionSkinFileUpload = document.getElementById("onionSkinUpload")
    let RemoveOnionSkin = document.getElementById("RemoveOnionSkin")
    let OnionOpacityText = document.getElementById("OnionOpacity")
    let CurrentOnionSkin = null;
    let CurrentOnionCanvas = null;
    let OnionOpacity = 0.5
    let OnionX = 0;
    let OnionY = 0;
    let OnionScale = 1;

    let OnionMoveButton = document.getElementById("OnionMoveButton")
    let OnionScaleButton = document.getElementById("OnionScaleButton")
    let OnionOpacButton = document.getElementById("OnionOpacButton")

    RemoveOnionSkin.onclick = () => {
        if (CurrentOnionCanvas) {
            CurrentOnionSkin = null;
            CurrentOnionCanvas.remove();
            CurrentOnionCanvas = null;
        };
    }

    OnionMoveButton.onmousedown = () => {

        if (CurrentOnionCanvas == null) return;

        let previousPos = [mouseX, mouseY];

        let interval = setInterval(() => {

            if (mousedown == false) {
                clearInterval(interval);
                return;
            }

            let newPos = [mouseX, mouseY];
            let delta = [newPos[0] - previousPos[0], newPos[1] - previousPos[1]];
            previousPos = newPos;

            let bounding = [0.09, 0.540, 0.98, 0.76];
            let boundingPixel = [22, 208, 252, 289];

            let pos = document.getElementsByTagName("canvas")[0].getBoundingClientRect()

            delta[0] /= pos.width;
            delta[1] /= pos.height;

            let xRate = (boundingPixel[2] - boundingPixel[0]) / (bounding[2] - bounding[0])
            let yRate = (boundingPixel[3] - boundingPixel[1]) / (bounding[3] - bounding[1])

            delta[0] *= xRate;
            delta[1] *= yRate;

            OnionX += delta[0];
            OnionY -= delta[1];

            moveOnionSkin()

        }, 100);

    }

    OnionScaleButton.onmousedown = () => {

        if (CurrentOnionCanvas == null) return;

        let previousPos = [mouseX, mouseY];

        let interval = setInterval(() => {

            if (mousedown == false) {
                clearInterval(interval);
                return;
            }

            let newPos = [mouseX, mouseY];
            let delta = [newPos[0] - previousPos[0], newPos[1] - previousPos[1]];
            previousPos = newPos;

            let bounding = [0.09, 0.540, 0.98, 0.76];
            let boundingPixel = [22, 208, 252, 289];

            let pos = document.getElementsByTagName("canvas")[0].getBoundingClientRect()

            delta[0] /= pos.width;
            delta[1] /= pos.height;

            let xRate = (boundingPixel[2] - boundingPixel[0]) / (bounding[2] - bounding[0])
            let yRate = (boundingPixel[3] - boundingPixel[1]) / (bounding[3] - bounding[1])

            delta[0] *= xRate;
            delta[1] *= yRate;

            let scaleFactor = 1 - delta[1] / 80;

            if (scaleFactor < 0.75) scaleFactor = 0.75;
            if (scaleFactor > 1.25) scaleFactor = 1.25;

            OnionScale *= scaleFactor
            if (OnionScale < 0.05) OnionScale = 0.05;

            moveOnionSkin()

        }, 100);

    }

    OnionOpacButton.onmousedown = () => {

        if (CurrentOnionCanvas == null) return;

        let previousPos = [mouseX, mouseY];

        let interval = setInterval(() => {

            if (mousedown == false) {
                clearInterval(interval);
                return;
            }

            let newPos = [mouseX, mouseY];
            let delta = [newPos[0] - previousPos[0], newPos[1] - previousPos[1]];
            previousPos = newPos;

            let bounding = [0.09, 0.540, 0.98, 0.76];
            let boundingPixel = [22, 208, 252, 289];

            let pos = document.getElementsByTagName("canvas")[0].getBoundingClientRect()

            delta[0] /= pos.width;
            delta[1] /= pos.height;

            let xRate = (boundingPixel[2] - boundingPixel[0]) / (bounding[2] - bounding[0])
            let yRate = (boundingPixel[3] - boundingPixel[1]) / (bounding[3] - bounding[1])

            delta[0] *= xRate;
            delta[1] *= yRate;

            let scaleFactor = 1 - delta[1] / 20;

            if (scaleFactor < 0.75) scaleFactor = 0.75;
            if (scaleFactor > 1.25) scaleFactor = 1.25;

            OnionOpacity *= scaleFactor
            if (OnionOpacity < 0) OnionOpacity = 0;
            if (OnionOpacity > 1) OnionOpacity = 1;

            moveOnionSkin()

        }, 100);

    }

    OnionSkinFileUpload.onchange = function (event) {
        var files = event.target.files;
        if (files && files.length > 0) {
            var reader = new FileReader();
            reader.readAsDataURL(files[0])
            reader.onload = function (e) {
                CurrentOnionSkin = new Image();
                CurrentOnionSkin.onload = function () {
                    console.log("%c Changed Onion-skin", 'font-size: 18px; color: #4feaff')
                    drawOnionSkin();
                }
                CurrentOnionSkin.src = e.target.result;
            }
        }
    };

    function moveOnionSkin() {
        if (CurrentOnionCanvas) {
            CurrentOnionCanvas.style.bottom = OnionY + "px"
            CurrentOnionCanvas.style.left = `calc(50% + ${OnionX}px)`
            CurrentOnionCanvas.style.opacity = OnionOpacity
            CurrentOnionCanvas.style.transform = `translate(-50%, 50%) scale(${OnionScale}, ${OnionScale})`
            OnionOpacityText.textContent = Math.round(OnionOpacity * 1000) / 1000
        }
    }

    function drawOnionSkin() {
        if (!CurrentOnionSkin)
            return;

        if (CurrentOnionCanvas) {
            CurrentOnionCanvas.remove()
        }

        CurrentOnionCanvas = document.createElement("canvas");

        CurrentOnionCanvas.width = CurrentOnionSkin.width;
        CurrentOnionCanvas.height = CurrentOnionSkin.height;

        OnionScale = 1

        OnionX = 0;
        OnionY = CurrentOnionCanvas.height / 2;

        let ctx = CurrentOnionCanvas.getContext("2d");
        ctx.imageSmoothingEnabled = false;
        ctx.drawImage(CurrentOnionSkin, 0, 0, CurrentOnionCanvas.width, CurrentOnionCanvas.height);

        document.getElementById("root").appendChild(CurrentOnionCanvas)

        CurrentOnionCanvas.style = "position: absolute;pointer-events:none;z-index:50;"

        moveOnionSkin()
    }

    // Port of Pictobot
    //
    // made by oeci, not me!!! I just added Userscript support!!!
    let PictobotSection = document.getElementById("PictobotSection")
    let BotStatus = document.getElementById("BotStatus")
    let BrightnessAmount = document.getElementById("BrightnessAmount")
    let ContrastAmount = document.getElementById("ContrastAmount")

    let pixelX = null;
    let pixelY = null;
    let pixelWidth = null;
    let pixelHeight = null;
    let powerFactor = 1;
    let brightness = 1;
    let pixels = 3000;

    function createBotGUI() {

        let move = document.createElement("button");
        let scale = document.createElement("button");
        let bright = document.createElement("button");
        let powUp = document.createElement("button");
        let powDown = document.createElement("button");
        let pixelCount = document.createElement("button");

        move.innerHTML = "Move";
        scale.innerHTML = "Scale";
        bright.innerHTML = "Gamma";
        powUp.innerHTML = "Contrast Up";
        powDown.innerHTML = "Contrast Down";
        pixelCount.innerHTML = "Resolution: " + pixels;

        let parent = document.createElement("div");

        let buttons = [scale, bright, move, powUp, powDown, pixelCount];

        let ref = document.getElementsByTagName("canvas")[0].getBoundingClientRect();

        let fontSize = 15;
        let widthRef = ref.width;

        let buttonWidth = widthRef / 7;

        for (var i = 0; i < buttons.length; i++) {

            parent.appendChild(buttons[i]);
            buttons[i].style.width = "33%"
            buttons[i].style.height = buttonWidth + "px"
            buttons[i].style.fontSize = fontSize + "px"
            buttons[i].style.verticalAlign = "top";
            buttons[i].style.background = "url('../images/intro_bg.png') no-repeat";
            buttons[i].style.backgroundSize = "750px"
            buttons[i].style.cursor = "pointer"
            buttons[i].style.fontFamily = "nds"

        }

        parent.style.display = "flex";
        parent.style.zIndex = "100";
        parent.style.width = "100%";
        parent.style.flexDirection = "row";
        parent.style.marginTop = "4px";
        parent.style.flexWrap = "wrap";

        powUp.onmousedown = () => {

            if (theimage == null) return;

            powerFactor++;
            powUptext()
            doDrawing();

        }

        powDown.onmousedown = () => {

            if (theimage == null) return;

            powerFactor--;
            if (powerFactor < 1) powerFactor = 1
            powUptext()
            doDrawing();

        }

        function powUptext() {
            ContrastAmount.textContent = powerFactor;
        }

        move.onmousedown = () => {

            if (theimage == null) return;

            let previousPos = [mouseX, mouseY];

            let interval = setInterval(() => {

                if (mousedown == false) {
                    clearInterval(interval);
                    return;
                }

                let newPos = [mouseX, mouseY];
                let delta = [newPos[0] - previousPos[0], newPos[1] - previousPos[1]];
                previousPos = newPos;

                let bounding = [0.09, 0.540, 0.98, 0.76];
                let boundingPixel = [22, 208, 252, 289];

                let pos = document.getElementsByTagName("canvas")[0].getBoundingClientRect()

                delta[0] /= pos.width;
                delta[1] /= pos.height;

                let xRate = (boundingPixel[2] - boundingPixel[0]) / (bounding[2] - bounding[0])
                let yRate = (boundingPixel[3] - boundingPixel[1]) / (bounding[3] - bounding[1])

                delta[0] *= xRate;
                delta[1] *= yRate;

                pixelX += delta[0];
                pixelY += delta[1];

                doDrawing()

            }, 100);

        }

        scale.onmousedown = () => {

            if (theimage == null) return;

            let previousPos = [mouseX, mouseY];

            let interval = setInterval(() => {

                if (mousedown == false) {
                    clearInterval(interval);
                    return;
                }

                let newPos = [mouseX, mouseY];
                let delta = [newPos[0] - previousPos[0], newPos[1] - previousPos[1]];
                previousPos = newPos;

                let bounding = [0.09, 0.540, 0.98, 0.76];
                let boundingPixel = [22, 208, 252, 289];

                let pos = document.getElementsByTagName("canvas")[0].getBoundingClientRect()

                delta[0] /= pos.width;
                delta[1] /= pos.height;

                let xRate = (boundingPixel[2] - boundingPixel[0]) / (bounding[2] - bounding[0])
                let yRate = (boundingPixel[3] - boundingPixel[1]) / (bounding[3] - bounding[1])

                delta[0] *= xRate;
                delta[1] *= yRate;

                pixelWidth += delta[1];
                pixelHeight = pixelWidth * theimage.width / theimage.height;

                doDrawing()

            }, 100);

        }

        bright.onmousedown = () => {

            if (theimage == null) return;

            let previousPos = [mouseX, mouseY];

            let interval = setInterval(() => {

                if (mousedown == false) {
                    clearInterval(interval);
                    return;
                }

                let newPos = [mouseX, mouseY];
                let delta = [newPos[0] - previousPos[0], newPos[1] - previousPos[1]];
                previousPos = newPos;

                let bounding = [0.09, 0.540, 0.98, 0.76];
                let boundingPixel = [22, 208, 252, 289];

                let pos = document.getElementsByTagName("canvas")[0].getBoundingClientRect()

                delta[0] /= pos.width;
                delta[1] /= pos.height;

                let xRate = (boundingPixel[2] - boundingPixel[0]) / (bounding[2] - bounding[0])
                let yRate = (boundingPixel[3] - boundingPixel[1]) / (bounding[3] - bounding[1])

                delta[0] *= xRate;
                delta[1] *= yRate;

                let scaleFactor = 1 + delta[1] / 20;

                if (scaleFactor < 0.75) scaleFactor = 0.75;
                if (scaleFactor > 1.25) scaleFactor = 1.25;

                brightness *= scaleFactor;
                if (brightness < 0.05) brightness = 0.05;

                BrightnessAmount.textContent = Math.round(((1 - brightness) + 1) * 1000) / 1000

                doDrawing()

            }, 100);

        }

        pixelCount.onmousedown = () => {

            if (theimage == null) return;

            let previousPos = [mouseX, mouseY];

            let interval = setInterval(() => {

                if (mousedown == false) {
                    clearInterval(interval);
                    return;
                }

                let newPos = [mouseX, mouseY];
                let delta = [newPos[0] - previousPos[0], newPos[1] - previousPos[1]];
                previousPos = newPos;

                let bounding = [0.09, 0.540, 0.98, 0.76];
                let boundingPixel = [22, 208, 252, 289];

                let pos = document.getElementsByTagName("canvas")[0].getBoundingClientRect()

                delta[0] /= pos.width;
                delta[1] /= pos.height;

                let xRate = (boundingPixel[2] - boundingPixel[0]) / (bounding[2] - bounding[0])
                let yRate = (boundingPixel[3] - boundingPixel[1]) / (bounding[3] - bounding[1])

                delta[0] *= xRate;
                delta[1] *= yRate;

                pixels += delta[1] * 50;
                pixels = Math.floor(pixels);
                if (pixels < 100) pixels = 100;
                pixelCount.innerHTML = "Resolution: " + pixels;

                doDrawing()

            }, 100);

        }


        PictobotSection.appendChild(parent)
    }

    function doDrawing() {

        if (theimage == null) return;

        drawImage(pixelX, pixelY, pixelWidth, pixelHeight, theimage, brightness, powerFactor, pixels);
        drawDrawing();

    }


    let pixelPrecision = 1000;

    let drawImage = (posx, posy, poswidth, posheight, img, brightness, powerFactor, maxPixels) => {

        drawHistory = [{
            x: 0,
            y: 0,
            type: 3
        }]

        let canvas = document.createElement("canvas");

        let pixelscale = Math.sqrt(maxPixels / (img.width * img.height))
        let width = Math.floor(img.width * pixelscale);
        let height = Math.floor(img.height * pixelscale);

        canvas.width = width;
        canvas.height = height;

        let ctx = canvas.getContext("2d");
        ctx.imageSmoothingEnabled = false;
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

        let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        let data = imageData.data;

        let scale = poswidth / width;

        for (var y = 0; y < canvas.height; y++) {

            for (var x = 0; x < canvas.width; x++) {

                let index = (y * canvas.width + x) * 4;

                if (data[index + 3] == 0) continue;

                let darkness = (data[index] + data[index + 1] + data[index + 2]) / 3

                darkness = -darkness + 255;

                let length = darkness / 255;

                length = length ** powerFactor;
                length *= brightness;

                if (length > 1) length = 1;

                let PoffsetX = (1 - length) / 2;
                let PoffsetX2 = 1 - (1 - length) / 2

                let PoffsetY = PoffsetX;
                let PoffsetY2 = PoffsetX2;

                if ((x + y) % 2 == 1) [PoffsetX, PoffsetX2] = [PoffsetX2, PoffsetX];

                let p1 = {
                    x: (x + PoffsetX) * scale + posx,
                    y: (y + PoffsetY) * scale + posy,
                    type: 2
                };
                let p2 = {
                    x: (x + PoffsetX2) * scale + posx,
                    y: (y + PoffsetY2) * scale + posy,
                    type: 0
                }

                p1.x = Math.floor(p1.x * pixelPrecision) / pixelPrecision;
                p1.y = Math.floor(p1.y * pixelPrecision) / pixelPrecision;
                p2.x = Math.floor(p2.x * pixelPrecision) / pixelPrecision;
                p2.y = Math.floor(p2.y * pixelPrecision) / pixelPrecision;

                drawHistory.push(p1)
                drawHistory.push(p2)

            }

        }


    }

    let mousedown = false;


    let theimage = null;
    window.addEventListener("dragover", function (e) {
        e = e || event;
        e.preventDefault();
    });

    let mouseX = 0;
    let mouseY = 0;

    window.addEventListener("drop", function (e) {
        e = e || event;
        e.preventDefault();

        for (var i = 0; i < e.dataTransfer.items.length; i++) {

            let item = e.dataTransfer.items[i];
            if (item.kind == "file") {
                var blob = item.getAsFile();
                var reader = new FileReader();
                reader.onload = function (event) {

                    theimage = new Image();
                    theimage.src = event.target.result;
                    theimage.crossOrigin = "Anonymous";
                    theimage.style.position = "absolute";
                    theimage.style.width = "400px";

                    theimage.onload = () => {

                        let bounding = [0.09, 0.540, 0.98, 0.76];
                        let boundingPixel = [22, 208, 252, 289];

                        let pos = document.getElementsByTagName("canvas")[0].getBoundingClientRect()

                        let dX = (mouseX - pos.x) / pos.width;
                        let dY = (mouseY - pos.y) / pos.height;

                        pixelX = 30
                        pixelY = 210;

                        let width = parseInt(theimage.style.width) / pos.width;

                        pixelWidth = 100;
                        pixelHeight = Math.floor(theimage.height * pixelWidth / theimage.width);

                        doDrawing()

                    }

                };
                reader.readAsDataURL(blob);
            }

        }

    }, false);

    window.addEventListener("paste", function (e) {


        var items = (event.clipboardData || event.originalEvent.clipboardData).items;

        for (index in items) {
            var item = items[index];
            if (item.kind === 'file') {
                var blob = item.getAsFile();
                var reader = new FileReader();
                reader.onload = function (event) {

                    theimage = new Image();
                    theimage.src = event.target.result;
                    theimage.crossOrigin = "Anonymous";
                    theimage.style.position = "absolute";
                    theimage.style.width = "400px";

                    theimage.onload = () => {

                        let bounding = [0.09, 0.540, 0.98, 0.76];
                        let boundingPixel = [22, 208, 252, 289];

                        let pos = document.getElementsByTagName("canvas")[0].getBoundingClientRect()

                        let dX = (mouseX - pos.x) / pos.width;
                        let dY = (mouseY - pos.y) / pos.height;

                        pixelX = 30
                        pixelY = 210;

                        let width = parseInt(theimage.style.width) / pos.width;

                        pixelWidth = 100;
                        pixelHeight = Math.floor(theimage.height * pixelWidth / theimage.width);

                        doDrawing()

                    }



                }; // data url!
                reader.readAsDataURL(blob);
            }
        }

    });



    window.addEventListener("mousemove", function (e) {

        mouseX = e.clientX;
        mouseY = e.clientY;

    }, false);

    window.addEventListener("mousedown", () => {

        mousedown = true;

    })

    window.addEventListener("mouseup", () => {

        mousedown = false;

    })

    window.addEventListener("keyup", function (e) {

        if (theimage != null) {

            let char = String.fromCharCode(e.keyCode);

            if (char == "S") {

                let currWidth = parseInt(theimage.style.width.replace("px", ""));
                currWidth += 30;
                theimage.style.width = currWidth + "px";
            }
            if (char == "W") {

                let currWidth = parseInt(theimage.style.width.replace("px", ""));
                currWidth -= 30;
                theimage.style.width = currWidth + "px";
            }

            e.preventDefault();

        }


    })

    function cleanupDrawing(drawing) {
        let fard = '';
        for (let i = 0; i < drawing.length; i++) {
            fard += drawing[i].type;
        }
        fard = fard.replace(/([34]+)([34])|([567]+)([567])/g, (_, a, b, c, d) => '_'.repeat((a ? a : c).length) + (b ? b : d));
        let prev = 5;
        let prev2 = null;
        return drawing.filter((v, i) => {
            if (fard[i] === '_') return false;
            if (v.type === prev || v.type === prev2) return false;
            if (v.type === 3 || v.type === 4) {
                prev2 = v.type;
            } else if (v.type === 5 || v.type === 6 || v.type === 7) {
                prev = v.type;
            }
            return true;
        });
    }

    function drawDrawing() {
        pc_sprites.drawing.clear();
        pc_sprites.drawing.drawMode = 0;
        pc_sprites.drawing.rainbowDeg = 0;
        for (let i = 0; i < drawHistory.length; i++) {
            let action = drawHistory[i];
            switch (action.type) {
                case 0: {
                    pc_sprites.drawing.lineTo(action.x, action.y);
                    if (pc_sprites.drawing.drawMode === 0xffffff) pc_sprites.drawing.rainbowDeg = (pc_sprites.drawing.rainbowDeg + 12) % 360;
                    break;
                }
                case 1: {
                    pc_sprites.drawing.moveTo(action.x, action.y);
                    break;
                }
                case 2: {
                    pc_sprites.drawing.moveTo(action.x, action.y);
                    break;
                }
                case 3: {
                    pc_sprites.drawing.drawWidth = 2;
                    break;
                }
                case 4: {
                    pc_sprites.drawing.drawWidth = 1;
                    break;
                }
                case 5: {
                    pc_sprites.drawing.drawMode = 0;
                    break;
                }
                case 6: {
                    pc_sprites.drawing.drawMode = 0xfbfbfb;
                    break;
                }
                case 7: {
                    pc_sprites.drawing.drawMode = 0xffffff;
                    break;
                }
            }
            if (pc_sprites.drawing.drawMode === 0xffffff) {
                pc_sprites.drawing.lineStyle(pc_sprites.drawing.drawWidth, hsl2rgb2dec(pc_sprites.drawing.rainbowDeg, 1, 0.5));
            } else {
                pc_sprites.drawing.lineStyle(pc_sprites.drawing.drawWidth + ((pc_sprites.drawing.drawMode > 0) * (pc_sprites.drawing.drawWidth === 2)), pc_sprites.drawing.drawMode);
            }
        }
    }
})();