Gartic Auto Draw

Automatically draws an image on Gartic using dithering techniques.

目前为 2024-04-12 提交的版本。查看 最新版本

// ==UserScript==
// @name         Gartic Auto Draw
// @namespace    gartic-auto-draw
// @description  Automatically draws an image on Gartic using dithering techniques.
// @version      1.3
// @license      MIT
// @author       EmersonxD
// @match        https://gartic.io/*
// @grant        none
// ==/UserScript==

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 = 10) {
  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);