- // ==UserScript==
- // @name Animation
- // @namespace https://greasyfork.org/users/281093
- // @match https://sketchful.io/
- // @grant none
- // @version 0.4
- // @author Bell
- // @license MIT
- // @copyright 2020, Bell (https://openuserjs.org/users/Bell)
- // @require https://greasyfork.org/scripts/408199-jsgif/code/jsgif.js?version=833926
- // @description Animation tools for Sketchful.io
- // ==/UserScript==
- /* jshint esversion: 6 */
-
- const containerStyle = `white-space: nowrap;
- overflow: auto;
- justify-content:center;
- margin-top: 10px;
- max-width: 76%;
- height: 124px;
- background: rgb(0 0 0 / 30%);
- padding: 10px;
- overflow-y: hidden;
- border-radius: 10px;
- margin-bottom: 5px;
- margin-left: 5vw;
- width: 100%;`;
-
- const layerPreviewStyle = `width: 133px;
- cursor: pointer;
- margin-right: 5px;`;
-
- const canvasLayerStyle = `width: 100%;
- position: absolute;
- pointer-events: none;
- image-rendering: pixelated;
- filter: opacity(0.5);`;
-
- const styleRules = [
- '#layerContainer::-webkit-scrollbar { width: 5px; height: 5px; overflow: hidden}',
- '#layerContainer::-webkit-scrollbar-track { background: none }',
- '#layerContainer::-webkit-scrollbar-thumb { background: #F5BC09; border-radius: 5px }',
- `#layerContainer { ${containerStyle} }`,
- `.layer { ${canvasLayerStyle} }`,
- `#layerContainer img { ${layerPreviewStyle} }`,
- '#buttonContainer div { height: fit-content; margin-top: 10px; margin-left: 10px; }',
- '#buttonContainer { width: 15%; padding-top: 5px }',
- '#gifPreview { position: absolute; z-index: 1; width: 100%; image-rendering: pixelated }',
- ];
-
- const sheet = window.document.styleSheets[window.document.styleSheets.length - 1];
- const outerContainer = document.createElement('div');
- const canvasContainer = document.querySelector('#gameCanvas');
- const canvasInner = document.querySelector("#gameCanvasInner");
- const canvas = document.querySelector('#canvas');
- const ctx = canvas.getContext('2d');
- const encoder = new GIFEncoder();
- const contextLayers = [];
-
- (() => {
- addLayerContainer();
- addButtons()
- styleRules.forEach((rule) => sheet.insertRule(rule));
- const gameModeObserver = new MutationObserver(checkRoomType);
-
- gameModeObserver.observe(document.querySelector('.game'),
- { attributes: true });
- gameModeObserver.observe(canvas, { attributes: true });
- })();
-
- function checkRoomType() {
- outerContainer.style.display = isFreeDraw() ? 'flex' : 'none';
- const canvasLayer = document.querySelector("#canvasLayer");
- if (!canvasLayer) return;
- canvasLayer.style.display = isFreeDraw() ? "" : "none";
- }
-
- function addLayer() {
- // resetActiveLayer();
- const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
- saveLayer(imgData);
-
- const canvasLayer = createLayer();
- const layerCtx = canvasLayer.getContext('2d');
- layerCtx.putImageData(imgData, 0, 0);
- makeTransparent(layerCtx);
-
- const previousLayer = canvasInner.querySelector("#canvasLayer");
- if (previousLayer) previousLayer.remove();
- canvas.parentElement.insertBefore(canvasLayer, canvas);
- }
-
- function render(name, delay) {
- if (!contextLayers.length) return;
- encoder.setRepeat(0);
- encoder.setDelay(delay);
- encoder.start();
- contextLayers.forEach(layer => {
- encoder.addFrame(layer);
- });
- encoder.finish();
- encoder.download(name + ".gif");
- }
-
- function copyCtx(img) {
- const tempCanvas = document.createElement('canvas');
- tempCanvas.width = canvas.width;
- tempCanvas.height = canvas.height;
- const tempCtx = tempCanvas.getContext('2d');
- if (img.tagName) {
- tempCtx.drawImage(img, 0, 0);
- } else {
- tempCtx.putImageData(img, 0, 0);
- }
- return tempCtx;
- }
-
- function saveLayer(data) {
- const activeLayer = document.querySelector("#activeLayer");
- const container = document.querySelector("#layerContainer");
- const img = document.createElement("img");
- img.src = canvas.toDataURL();
-
- // if (activeLayer) {
- // insertAfter(img, activeLayer);
- // const index = nodeIndex(activeLayer);
- // contextLayers.splice(index, 0, copyCtx(img));
- // } else {
- container.append(img);
- contextLayers.push(copyCtx(data));
- // }
- }
-
- function insertAfter(newNode, referenceNode) {
- referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
- }
-
- function setActiveLayer(e) {
- const img = e.target;
- if (img.tagName !== "IMG") return;
- resetActiveLayer();
- const canvasLayerCtx = document.querySelector("#canvasLayer").getContext("2d");
- const previousImg = img.previousSibling;
- if (previousImg) {
- canvasLayerCtx.drawImage(previousImg, 0, 0);
- makeTransparent(canvasLayerCtx);
- } else {
- canvasLayerCtx.clearRect(0, 0, canvas.width, canvas.height);
- }
- img.id = "activeLayer";
- img.style.border = "3px solid red";
- ctx.drawImage(img, 0, 0);
- }
-
- function resetActiveLayer() {
- const layer = document.querySelector("#activeLayer");
- if (!layer) return;
- layer.id = "";
- layer.style.border = "";
- }
-
- function createLayer() {
- const canvasLayer = document.createElement('canvas');
- canvasLayer.classList.add("layer");
- canvasLayer.width = canvas.width;
- canvasLayer.height = canvas.height;
- canvasLayer.id = "canvasLayer";
- return canvasLayer;
- }
-
- function downloadGif(data, name) {
- let a = document.createElement("a");
- a.download = name + ".gif";
- a.href = data;
- a.click();
- }
-
- function addButton(text, clickFunction, element, type) {
- const button = document.createElement("div");
- button.setAttribute("class", `btn btn-sm btn-${type}`);
- button.textContent = text;
- button.onclick = clickFunction;
- element.append(button);
- return button;
- }
-
- function clamp(num, min, max) {
- return num <= min ? min : num >= max ? max : num;
- }
-
- function getInterval() {
- const input = document.querySelector("#gifIntervalInput");
- let interval = parseInt(input.value);
- if (isNaN(interval)) interval = 100;
- interval = clamp(interval, 17, 10000);
- input.value = interval;
- return interval;
- }
-
- function renderGif() {
- const interval = getInterval();
- const name = "sketchful-gif-" + Date.now();
- render(name, interval);
- }
-
- function removeLayer() {
- const activeLayer = document.querySelector('#activeLayer');
- const layerContainer = document.querySelector('#layerContainer');
- if (!activeLayer) return;
- const index = nodeIndex(activeLayer);
- contextLayers.splice(index, 1);
- activeLayer.remove();
- }
-
- function nodeIndex(node) {
- return Array.prototype.indexOf.call(node.parentNode.children, node);
- }
-
- function addButtons() {
- const buttonContainer = document.createElement("div");
- buttonContainer.id = "buttonContainer";
- outerContainer.append(buttonContainer);
- addButton("Save Gif", renderGif, buttonContainer, "warning");
- addButton("NOnion", toggleOnion, buttonContainer, "warning");
- addButton("Save Layer", addLayer, buttonContainer, "info");
- addButton("Delete Layer", removeLayer, buttonContainer, "danger");
- addButton("Play", playAnimation, buttonContainer, "success");
-
- const textDiv = document.createElement('div');
- const textInput = document.createElement("input");
- textDiv.classList.add('btn');
- textDiv.style.padding = '0px';
- textInput.placeholder = "Interval (ms)";
- textInput.style.width = "100px";
- textInput.id = "gifIntervalInput";
- setInputFilter(textInput, (v) => {return /^\d*\.?\d*$/.test(v);});
- textDiv.append(textInput);
- buttonContainer.append(textDiv);
- }
-
- function addLayerContainer() {
- const game = document.querySelector("body > div.game");
- const container = document.createElement("div");
-
- outerContainer.style.display = "flex";
- outerContainer.style.flexDirection = "row";
-
- container.addEventListener('wheel', (e) => {
- if (e.deltaY > 0) container.scrollLeft += 100;
- else container.scrollLeft -= 100;
- e.preventDefault();
- });
-
- container.addEventListener('pointerdown', setActiveLayer, true);
- container.id = "layerContainer";
- container.setAttribute("ondragstart", "return false");
- outerContainer.append(container);
-
- game.append(outerContainer);
- }
-
- function toggleOnion(e) {
- const canvasLayer = document.querySelector("#canvasLayer");
- if (!canvasLayer) return;
-
- if (this.textContent === "NOnion") {
- canvasLayer.style.display = "none";
- this.textContent = "Onion";
- } else {
- canvasLayer.style.display = "";
- this.textContent = "NOnion";
- }
- }
-
- let animating = null;
- function playAnimation(e) {
- if (this.textContent === "Stop") {
- this.classList.toggle("btn-success");
- this.classList.toggle("btn-danger");
- this.textContent = "Play";
- if (animating) clearInterval(animating);
- const preview = document.querySelector("#gifPreview");
- if (preview) preview.remove();
- return;
- }
-
- const canvasCover = document.querySelector("#canvasCover");
- const layerContainer = document.querySelector("#layerContainer");
- const img = document.createElement('img');
- img.style.imageRendering = "pixelated";
- img.id = "gifPreview";
-
- canvasCover.parentElement.insertBefore(img, canvasCover);
-
- let frame = layerContainer.firstChild;
- if (!frame) return;
- const interval = getInterval();
-
- this.classList.toggle("btn-success");
- this.classList.toggle("btn-danger");
- this.textContent = "Stop";
-
- animating = setInterval(() => {
- img.src = frame.src;
- frame = frame.nextSibling || layerContainer.firstChild;
- }, interval);
- }
-
- function isFreeDraw() {
- return (
- document.querySelector("#canvas").style.display !== 'none' &&
- document.querySelector('#gameClock').style.display === 'none' &&
- document.querySelector('#gameSettings').style.display === 'none'
- );
- }
-
- function setInputFilter(textbox, inputFilter) {
- ["input", "keydown", "keyup", "mousedown",
- "mouseup", "select", "contextmenu", "drop"].forEach(function(event) {
- textbox.addEventListener(event, function() {
- if (inputFilter(this.value)) {
- this.oldValue = this.value;
- this.oldSelectionStart = this.selectionStart;
- this.oldSelectionEnd = this.selectionEnd;
- } else if (this.hasOwnProperty("oldValue")) {
- this.value = this.oldValue;
- this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
- } else {
- this.value = "";
- }
- });
- });
- }
-
- function makeTransparent(context) {
- const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
- const data = imgData.data;
-
- for(let i = 0; i < data.length; i += 4) {
- const [r, g, b] = data.slice(i, i + 3);
- if (r >= 230 && g >= 230 && b >= 230) {
- data[i + 3] = 0;
- }
- }
-
- context.putImageData(imgData, 0, 0);
- }