Greasy Fork is available in English.

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.6
// @license      MIT
// @author       EmersonxD
// @match        https://gartic.io/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function () {
  'use strict';

  // Função para inverter um dicionário (mapa)
  function invertDict(d) {
    return Object.keys(d).reduce((acc, key) => {
      acc[d[key]] = key;
      return acc;
    }, {});
  }

  // Função para criar um mapa de dithering
  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;
  }

  // Função para clicar em um ponto com retentativas
  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);
    });
  }

  // Função principal para desenhar a imagem
  async function createDrawing(image, scale = 100, interval = 2) {
    if (!image || !image.type.startsWith("image/")) {
      alert("Por favor, selecione um arquivo de imagem válido.");
      return;
    }

    const canvas = document.querySelector(".game.canvas");
    if (!canvas) {
      alert("Canvas do Gartic não encontrado. Certifique-se de estar na página correta.");
      return;
    }

    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 = [];

    // Redimensiona a imagem para o tamanho do canvas
    const width = canvas.offsetWidth;
    const height = canvas.offsetHeight;
    const tempCanvas = document.createElement("canvas");
    tempCanvas.width = width;
    tempCanvas.height = height;
    const tempContext = tempCanvas.getContext("2d");
    tempContext.drawImage(img, 0, 0, width, height);

    // Obtém os pixels da imagem redimensionada
    const imageData = tempContext.getImageData(0, 0, width, height).data;

    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        const index = (y * width + x) * 4;
        const r = imageData[index];
        const g = imageData[index + 1];
        const b = imageData[index + 2];
        const gray = Math.round((r + g + b) / 3);
        const tone = map[gray];
        pixels.push({ x, y, tone });
      }
    }

    // Desenha os pixels no canvas do Gartic
    for (let i = 0; i < pixels.length; i++) {
      const { x, y, tone } = pixels[i];

      try {
        await clickWithRetry(x + canvas.offsetLeft, y + 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));
    }

    alert("Desenho concluído!");
  }

  // Cria a interface do usuário
  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.style.padding = "10px";
  button.style.backgroundColor = "#4CAF50";
  button.style.color = "#fff";
  button.style.border = "none";
  button.style.borderRadius = "5px";
  button.style.cursor = "pointer";

  button.addEventListener("click", () => {
    input.click();
  });

  document.body.appendChild(button);
})();