// ==UserScript==
// @name Gartic Auto Draw
// @namespace gartic-auto-draw
// @description Automatically draws an image on Gartic using dithering techniques.
// @version 1.4
// @license MIT
// @author EmersonxD
// @match https://gartic.io/*
// @grant none
// @run-at document-idle
// @require https://cdn.jsdelivr.net/npm/esversion@9.1.2/esversion.min.js
// ==/UserScript==
(function() {
'use strict';
// use esversion: 8;
function invertDict(d) {
return Object.keys(d).reduce((acc, key) => {
acc[d[key]] = key;
return acc;
}, {});
}
function createDitherMap(numTones) {
const step = 256 / (numTones - 1);
const map = {};
for (let i = 0; i < 256; i++) {
const value = Math.round(i / step) * step;
map[i] = value;
}
return map;
}
async function clickWithRetry(x, y, button = "left", retries = 5) {
const buttonCode = { left: 0, middle: 1, right: 2 }[button];
const element = document.elementFromPoint(x, y);
if (!element) return false;
const event = new MouseEvent("mousedown", {
bubbles: true,
cancelable: true,
view: window,
button: buttonCode,
buttons: 1,
clientX: x,
clientY: y,
screenX: x,
screenY: y,
});
element.dispatchEvent(event);
if (retries <= 0) return false;
const retry = () => clickWithRetry(x, y, button, retries - 1);
return new Promise((resolve) => {
setTimeout(() => {
if (document.activeElement !== element) {
retry().then(resolve);
} else {
const event = new MouseEvent("mouseup", {
bubbles: true,
cancelable: true,
view: window,
button: buttonCode,
buttons: 1,
clientX: x,
clientY: y,
screenX: x,
screenY: y,
});
document.activeElement.dispatchEvent(event);
resolve(true);
}
}, 50);
});
}
async function createDrawing(image, scale = 100, interval = 2) {
if (!image || !image.type.startsWith("image/")) {
console.error("Por favor, selecione um arquivo de imagem válido.");
return;
}
const canvas = document.querySelector(".game.canvas");
const context = canvas.getContext("2d");
const img = await new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => resolve(img);
img.src = event.target.result;
};
reader.readAsDataURL(image);
});
const map = createDitherMap(scale);
const pixels = [];
for (let y = 0; y < img.height; y++) {
for (let x = 0; x < img.width; x++) {
const pixel = context.getImageData(x, y, 1, 1).data;
const gray = Math.round((pixel[0] + pixel[1] + pixel[2]) / 3);
const tone = map[gray];
pixels.push({ x, y, tone });
}
}
for (let i = 0; i < pixels.length; i++) {
const { x, y, tone } = pixels[i];
const px = (x * canvas.offsetWidth) / img.width;
const py = (y * canvas.offsetHeight) / img.height;
try {
await clickWithRetry(
px + canvas.offsetLeft,
py + canvas.offsetTop,
"left",
5
);
const color = invertDict(map)[tone];
context.fillStyle = `#${color.toString(16).padStart(6, "0")}`;
context.fillRect(x, y, 1, 1);
} catch (error) {
console.error(`Erro ao desenhar o pixel (${x}, ${y}):`, error);
}
await new Promise((resolve) => setTimeout(resolve, interval));
}
}
const input = document.createElement("input");
input.type = "file";
input.style.display = "none";
input.addEventListener("change", () => {
const file = input.files[0];
createDrawing(file);
});
document.body.appendChild(input);
const button = document.createElement("button");
button.type = "button";
button.innerText = "Desenhar";
button.style.position = "fixed";
button.style.top = "10px";
button.style.right = "10px";
button.style.zIndex = "9999";
button.addEventListener("click", () => {
input.click();
});
document.body.appendChild(button);
})();