Gartic Auto Draw

Automatically draws an image on Gartic using dithering techniques.

// ==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);

})();