Gartic Drawer

Gartic.io draw / Draw in Gartic / draw / Gartic auto script

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Gartic Drawer
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Gartic.io draw / Draw in Gartic / draw / Gartic auto script
// @author       F̷a̷r̷e̷s̷0
// @match        https://gartic.io/*
// @grant        none
// @license      MIT
// @icon         https://i.ibb.co/7d9y9T03/1777940315338.png
// @locale       ar
// ==/UserScript==

(function () {
"use strict";

const CW = 767, CH = 448;
const CFG = { delay: 120 };

// state
let timeoutIds = new Set();
let drawing = false;
let globalSegs = [];
let globalColorMode = false;
let lastIndex = 0;

// vector state
let vectorAnimationId = null;
let vectorDrawCancel = false;

// --- الوظائف المساعدة ---

function hexToRgb(hex) {
  const r = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return r ? { r: parseInt(r[1],16), g: parseInt(r[2],16), b: parseInt(r[3],16) } : null;
}

function sampleColorClassic(x1, y1, x2, rgba) {
  const mx = Math.max(0, Math.min(CW-1, Math.round((x1+x2)/2)));
  const my = Math.max(0, Math.min(CH-1, Math.round(y1)));
  const i = (my * CW + mx) * 4;
  return { r: rgba[i], g: rgba[i+1], b: rgba[i+2] };
}

function processImageClassic(imgEl, threshold, contrast) {
  const cv = document.createElement("canvas");
  cv.width = CW; cv.height = CH;
  const ctx = cv.getContext("2d");
  const scale = Math.min(CW / imgEl.naturalWidth, CH / imgEl.naturalHeight);
  const w = imgEl.naturalWidth * scale;
  const h = imgEl.naturalHeight * scale;
  const ox = (CW-w)/2, oy = (CH-h)/2;
  ctx.fillStyle = "#fff";
  ctx.fillRect(0, 0, CW, CH);
  ctx.drawImage(imgEl, ox, oy, w, h);
  const raw = ctx.getImageData(0, 0, CW, CH);
  const data = raw.data;
  const gray = new Uint8Array(CW * CH);
  for (let i = 0; i < CW*CH; i++) {
    const idx = i*4, a = data[idx+3];
    const r = data[idx], g = data[idx+1], b = data[idx+2];
    if (a < 128 || (r > 240 && g > 240 && b > 240)) { gray[i] = 255; }
    else {
      let lum = 0.299*r + 0.587*g + 0.114*b;
      lum = Math.min(255, Math.max(0, ((lum-128)*contrast)+128));
      gray[i] = lum;
    }
  }
  const mask = new Uint8Array(CW*CH);
  for (let i = 0; i < CW*CH; i++) mask[i] = gray[i] < threshold ? 0 : 255;
  return { mask, rgba: data };
}

function collectImageSegmentsClassic(processed, step, colorMode, baseColor) {
  const { mask, rgba } = processed;
  const segs = [];
  function tag(s) {
    if (!colorMode || !baseColor) return s;
    const c = sampleColorClassic(s.x1, s.y1, s.x2, rgba);
    s.color = `rgb(${Math.round(c.r*0.7+baseColor.r*0.3)},${Math.round(c.g*0.7+baseColor.g*0.3)},${Math.round(c.b*0.7+baseColor.b*0.3)})`;
    return s;
  }
  for (let y = 0; y < CH; y += step) {
    let run = false, sx = 0;
    for (let x = 0; x < CW; x++) {
      const dark = mask[y*CW+x] < 128;
      if (dark && !run)  { run = true; sx = x; }
      else if (!dark && run) { run = false; segs.push(tag({x1:sx,y1:y,x2:x,y2:y})); }
    }
    if (run) segs.push(tag({x1:sx,y1:y,x2:CW-1,y2:y}));
  }
  return segs;
}

// --- gartic bağlantısı ---

function getDrawer() {
  const el = document.querySelector("div#answer");
  if (!el) return null;
  for (const k in el) {
    if (!k.startsWith("__react")) continue;
    const g = el[k]?.return?.stateNode?._game;
    if (!g) continue;
    return { d: g._desenho, rect: g._desenho._elemBase.getBoundingClientRect() };
  }
  return null;
}

function selectTool(toolId) {
  const btn = document.querySelector(`li#${toolId}`);
  if (btn && !btn.classList.contains('active')) btn.click();
}

// temizle diyalogu
function clickYesOnDialog() {
  let attempts = 0;
  const interval = setInterval(() => {
    attempts++;
    const buttons = Array.from(document.querySelectorAll('button, div[role="button"], .button, [class*="btn"]'));
    const yesBtn = buttons.find(b => {
      const t = b.textContent.trim().toUpperCase();
      return t === 'EVET' || t === 'YES' || t === 'TAMAM' || t === 'OK';
    });
    if (yesBtn) {
      yesBtn.click();
      clearInterval(interval);
      hardStop();
      setStatus("🗑تم التنظيف", false);
    } else if (attempts >= 25) {
      clearInterval(interval);
    }
  }, 120);
}

function clearCanvas() {
  const btn = document.querySelector("li#clean");
  if (btn) { btn.click(); clickYesOnDialog(); }
  else setStatus("❌ مفيش حاجه ", false);
}

// --- يشتغل / يقف---

function hardStop() {
  window.__garticBotStop = true;
  timeoutIds.forEach(id => clearTimeout(id));
  timeoutIds.clear();
  drawing = false;
  const resumeBtn = document.getElementById("gu-resume");
  if (resumeBtn) resumeBtn.style.display = (globalSegs.length > lastIndex) ? "block" : "none";
  setStatus(`⛔ متوقف (${lastIndex}/${globalSegs.length})`, false);
}

function stopDrawing() { hardStop(); stopVectorDrawing(); }

function resumeDrawing() {
  if (!globalSegs.length || lastIndex >= globalSegs.length) {
    setStatus("⚠️ مفيش صورة عشان تكمل الرسم", false); return;
  }
  if (globalColorMode) startDrawColored(globalSegs, lastIndex);
  else startDraw(globalSegs, lastIndex);
}

function sendLine(d, rect, x1, y1, x2, y2, delay, tool) {
  if (window.__garticBotStop) return;
  const mk = (x,y) => ({ clientX: rect.left+x, clientY: rect.top+y, preventDefault(){} });
  const id = setTimeout(() => {
    if (window.__garticBotStop) return;
    if (tool) selectTool(tool);
    d._eventMouseDown(mk(x1,y1));
    d._eventMouseMove(mk(x2,y2));
    d._eventMouseUp(mk(x2,y2));
  }, delay);
  timeoutIds.add(id);
  const cleanId = setTimeout(() => timeoutIds.delete(id), delay+2000);
  timeoutIds.add(cleanId);
}

// --- أدوات معالجه الصورة ---

function getImageData(imgEl) {
  const cv = document.createElement("canvas");
  cv.width = CW; cv.height = CH;
  const ctx = cv.getContext("2d");
  const scale = Math.min(CW/imgEl.naturalWidth, CH/imgEl.naturalHeight);
  const w_ = imgEl.naturalWidth*scale, h_ = imgEl.naturalHeight*scale;
  ctx.fillStyle = "#fff"; ctx.fillRect(0,0,CW,CH);
  ctx.drawImage(imgEl, (CW-w_)/2, (CH-h_)/2, w_, h_);
  return ctx.getImageData(0,0,CW,CH);
}

function toGray(rgba, contrast) {
  const gray = new Float32Array(CW*CH);
  for (let i = 0; i < CW*CH; i++) {
    const idx=i*4, a=rgba[idx+3];
    if (a<128) { gray[i]=255; continue; }
    let lum = 0.299*rgba[idx] + 0.587*rgba[idx+1] + 0.114*rgba[idx+2];
    gray[i] = Math.min(255, Math.max(0, ((lum-128)*contrast)+128));
  }
  return gray;
}

function gaussianBlur(gray, w, h) {
  const K=[1,4,7,4,1,4,16,26,16,4,7,26,41,26,7,4,16,26,16,4,1,4,7,4,1];
  const out = new Float32Array(w*h);
  for (let y=2;y<h-2;y++)
    for (let x=2;x<w-2;x++) {
      let s=0;
      for (let j=-2;j<=2;j++) for (let i=-2;i<=2;i++)
        s += gray[(y+j)*w+(x+i)] * K[(j+2)*5+(i+2)];
      out[y*w+x] = s/273;
    }
  for (let y=0;y<h;y++) for (let x=0;x<w;x++)
    if (y<2||y>=h-2||x<2||x>=w-2) out[y*w+x] = gray[y*w+x];
  return out;
}

function buildBackgroundMaskAuto(gray, w, h) {
  const N=10; let sum=0;
  for (let i=0;i<N;i++) {
    sum+=gray[Math.floor(i*w/N)]; sum+=gray[(h-1)*w+Math.floor(i*w/N)];
    sum+=gray[Math.floor(i*h/N)*w]; sum+=gray[Math.floor(i*h/N)*w+(w-1)];
  }
  const edgeAvg = sum/(N*4);
  const isDarkBg = edgeAvg < 100;
  const bgLevel = isDarkBg ? 55 : 220;
  const isBgPx = isDarkBg ? v=>v<=bgLevel : v=>v>=bgLevel;
  const bg=new Uint8Array(w*h), stack=[];
  const tryAdd=(x,y)=>{ if(x<0||x>=w||y<0||y>=h)return; const i=y*w+x; if(!bg[i]&&isBgPx(gray[i])){bg[i]=1;stack.push(i);} };
  for (let x=0;x<w;x++){tryAdd(x,0);tryAdd(x,h-1);}
  for (let y=1;y<h-1;y++){tryAdd(0,y);tryAdd(w-1,y);}
  while (stack.length) {
    const i=stack.pop(), x=i%w, y=Math.floor(i/w);
    if(x>0){const ni=i-1;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
    if(x<w-1){const ni=i+1;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
    if(y>0){const ni=i-w;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
    if(y<h-1){const ni=i+w;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
  }
  return { bg, isDarkBg };
}

function isSkinPixel(r,g,b) {
  const Y = 0.299*r+0.587*g+0.114*b;
  if (Y<50||Y>235) return false;
  const Cb = 128-0.16874*r-0.33126*g+0.5*b;
  const Cr = 128+0.5*r-0.41869*g-0.08131*b;
  return (Cb>=80&&Cb<=122)&&(Cr>=133&&Cr<=173);
}

function sampleColorNeighborhood(x1, x2, y, rgba, w) {
  const mx=Math.round((x1+x2)/2);
  const clamp=(v,max)=>Math.max(0,Math.min(max-1,v));
  let r=0,g=0,b=0,ws=0;
  const wt=[[1,2,1],[2,4,2],[1,2,1]];
  for (let dy=-1;dy<=1;dy++) for (let dx=-1;dx<=1;dx++) {
    const nx=clamp(mx+dx,w), ny=clamp(y+dy,CH);
    const idx=(ny*w+nx)*4, w_=wt[dy+1][dx+1];
    r+=rgba[idx]*w_; g+=rgba[idx+1]*w_; b+=rgba[idx+2]*w_; ws+=w_;
  }
  return `rgb(${Math.round(r/ws)},${Math.round(g/ws)},${Math.round(b/ws)})`;
}

function sampleColorRect(x, y, w, h, rgba, rw) {
  let r=0,g=0,b=0,cnt=0;
  const x2=Math.min(x+w,CW), y2=Math.min(y+h,CH);
  const step=Math.max(1,Math.floor(Math.min(w,h)/4));
  for (let py=y;py<y2;py+=step) for (let px=x;px<x2;px+=step) {
    const i=(py*rw+px)*4; r+=rgba[i]; g+=rgba[i+1]; b+=rgba[i+2]; cnt++;
  }
  if (!cnt) return 'rgb(128,128,128)';
  return `rgb(${Math.round(r/cnt)},${Math.round(g/cnt)},${Math.round(b/cnt)})`;
}

// --- دمج الاسطر ---

function dist(ax,ay,bx,by) { return Math.sqrt((ax-bx)**2+(ay-by)**2); }

function angleBetween(x1,y1,x2,y2,x3,y3) {
  const a1=Math.atan2(y2-y1,x2-x1), a2=Math.atan2(y3-y2,x3-x2);
  let diff=Math.abs(a1-a2);
  if (diff>Math.PI) diff=2*Math.PI-diff;
  return diff*(180/Math.PI);
}

function mergeCollinearSegments(segs, angleThr=18, gapThr=4) {
  if (!segs.length) return [];
  const merged = [];
  let cur = {...segs[0]};
  for (let i=1;i<segs.length;i++) {
    const s=segs[i];
    const d=dist(cur.x2,cur.y2,s.x1,s.y1);
    const ang=angleBetween(cur.x1,cur.y1,cur.x2,cur.y2,s.x1,s.y1);
    if (d<=gapThr&&ang<=angleThr&&(!cur.color||cur.color===s.color)) {
      cur.x2=s.x2; cur.y2=s.y2;
    } else { merged.push(cur); cur={...s}; }
  }
  merged.push(cur);
  return merged;
}

// ---وضع السرعه ---

function collectPortraitFill(gray, bgMask, rgba, w, h, maxStep, skipSkin, colorMode) {
  const rowScore=new Float32Array(h);
  for (let y=1;y<h-1;y++) {
    let score=0,cnt=0;
    for (let x=1;x<w-1;x++) {
      if (bgMask[y*w+x]) continue; cnt++;
      const gx=Math.abs(gray[y*w+(x+1)]-gray[y*w+(x-1)]);
      const gy=Math.abs(gray[(y+1)*w+x]-gray[(y-1)*w+x]);
      score+=gx+gy;
    }
    rowScore[y]=cnt>0 ? score/cnt+cnt*0.08 : 0;
  }
  const smoothed=new Float32Array(h); let maxVal=1e-6;
  for (let y=0;y<h;y++) {
    let s=0,c=0;
    for (let dy=-2;dy<=2;dy++) {
      const ny=y+dy; if(ny<0||ny>=h)continue;
      const wt=3-Math.abs(dy); s+=rowScore[ny]*wt; c+=wt;
    }
    smoothed[y]=s/c;
    if (smoothed[y]>maxVal) maxVal=smoothed[y];
  }
  let segs=[],y=0;
  while (y<h) {
    const t=smoothed[y]/maxVal;
    const step=Math.max(1,Math.round(maxStep*Math.pow(1-t,0.45)+0.5));
    let run=false,sx=0;
    for (let x=0;x<w;x++) {
      const i=y*w+x; let draw=false;
      if (!bgMask[i]) {
        const lum=gray[i];
        if (lum<70) draw=true;
        else {
          const ri=i*4;
          if (!(skipSkin&&isSkinPixel(rgba[ri],rgba[ri+1],rgba[ri+2]))&&lum<185) draw=true;
        }
      }
      if (draw&&!run){run=true;sx=x;}
      else if (!draw&&run) {
        run=false;
        const seg={x1:sx,y1:y,x2:x,y2:y};
        if (colorMode) seg.color=sampleColorNeighborhood(sx,x,y,rgba,w);
        segs.push(seg);
      }
    }
    if (run) { const seg={x1:sx,y1:y,x2:w-1,y2:y}; if(colorMode)seg.color=sampleColorNeighborhood(sx,w-1,y,rgba,w); segs.push(seg); }
    y += smoothed[y]/maxVal>0.6 ? 1 : step;
  }
  return mergeCollinearSegments(segs,25,6);
}

// --- الوضع الرباعي ---

function getAvgColorAndVariance(rgba, gray, x, y, w, h, rw) {
  let r=0,g=0,b=0,gs=0,gsq=0,cnt=0;
  for (let dy=y;dy<y+h;dy++) for (let dx=x;dx<x+w;dx++) {
    const i=(dy*rw+dx)*4;
    r+=rgba[i]; g+=rgba[i+1]; b+=rgba[i+2];
    const v=gray[dy*rw+dx]; gs+=v; gsq+=v*v; cnt++;
  }
  if (!cnt) return {var:99999,color:'rgb(0,0,0)'};
  const mean=gs/cnt;
  return { var:(gsq/cnt)-mean*mean, color:`rgb(${Math.round(r/cnt)},${Math.round(g/cnt)},${Math.round(b/cnt)})` };
}

function quadtreeSegments(rgba, gray, x, y, w, h, rw, maxDepth, varThr, depth=0) {
  const segs=[], info=getAvgColorAndVariance(rgba,gray,x,y,w,h,rw);
  if (depth>=maxDepth||w<8||h<8||info.var<varThr) {
    if (w>1&&h>1) segs.push({x1:x,y1:y,x2:x+w,y2:y+h,color:info.color,tool:'op2'});
    return segs;
  }
  const hw=Math.floor(w/2),hh=Math.floor(h/2),rw2=w-hw,rh2=h-hh;
  segs.push(...quadtreeSegments(rgba,gray,x,y,hw,hh,rw,maxDepth,varThr,depth+1));
  segs.push(...quadtreeSegments(rgba,gray,x+hw,y,rw2,hh,rw,maxDepth,varThr,depth+1));
  segs.push(...quadtreeSegments(rgba,gray,x,y+hh,hw,rh2,rw,maxDepth,varThr,depth+1));
  segs.push(...quadtreeSegments(rgba,gray,x+hw,y+hh,rw2,rh2,rw,maxDepth,varThr,depth+1));
  return segs;
}

function processQuadMode(imgEl, maxDepth, varThr, colorMode) {
  const raw=getImageData(imgEl);
  const gray=toGray(raw.data,1.0);
  const segs=quadtreeSegments(raw.data,gray,0,0,CW,CH,CW,maxDepth,varThr);
  if (!colorMode) segs.forEach(s=>delete s.color);
  return segs;
}

// --- وضع المتجهات ---

function vectorGaussianBlur(gray, w, h, sigma) {
  if (sigma<=0) return gray;
  const radius=Math.ceil(sigma*3), size=radius*2+1;
  const kernel=new Float32Array(size); let sum=0;
  for (let i=-radius;i<=radius;i++) { const v=Math.exp(-(i*i)/(2*sigma*sigma)); kernel[i+radius]=v; sum+=v; }
  for (let i=0;i<size;i++) kernel[i]/=sum;
  const tmp=new Float32Array(w*h);
  for (let y=0;y<h;y++) for (let x=0;x<w;x++) {
    let s=0;
    for (let k=-radius;k<=radius;k++) s+=gray[y*w+Math.min(Math.max(x+k,0),w-1)]*kernel[k+radius];
    tmp[y*w+x]=s;
  }
  const out=new Float32Array(w*h);
  for (let y=0;y<h;y++) for (let x=0;x<w;x++) {
    let s=0;
    for (let k=-radius;k<=radius;k++) s+=tmp[Math.min(Math.max(y+k,0),h-1)*w+x]*kernel[k+radius];
    out[y*w+x]=s;
  }
  return out;
}

function vectorImageToGrayscale(imageData) {
  const data=imageData.data, gray=new Float32Array(CW*CH);
  for (let i=0;i<CW*CH;i++) {
    const idx=i*4;
    if (data[idx+3]<128){gray[i]=255;continue;}
    gray[i]=0.299*data[idx]+0.587*data[idx+1]+0.114*data[idx+2];
  }
  return gray;
}

function marchingSquares(blurred, w, h, threshold, cellSize) {
  const gridW=Math.floor(w/cellSize)+1, gridH=Math.floor(h/cellSize)+1;
  const grid=new Float32Array(gridW*gridH);
  for (let y=0;y<gridH;y++) for (let x=0;x<gridW;x++)
    grid[y*gridW+x]=blurred[Math.min(y*cellSize,h-1)*w+Math.min(x*cellSize,w-1)];

  const edges=[];
  function interp(v1,v2,faceLen,bx,by,horiz) {
    if (Math.abs(v2-v1)<1e-6) return {x:bx,y:by};
    const t=(threshold-v1)/(v2-v1);
    return horiz?{x:bx+t*faceLen,y:by}:{x:bx,y:by+t*faceLen};
  }
  for (let j=0;j<gridH-1;j++) for (let i=0;i<gridW-1;i++) {
    const x0=i*cellSize, y0=j*cellSize;
    const v0=grid[j*gridW+i], v1=grid[j*gridW+(i+1)];
    const v2=grid[(j+1)*gridW+(i+1)], v3=grid[(j+1)*gridW+i];
    const b0=v0<threshold, b1=v1<threshold, b2=v2<threshold, b3=v3<threshold;
    const idx=(b0?1:0)|(b1?2:0)|(b2?4:0)|(b3?8:0);
    if (idx===0||idx===15) continue;
    let top,right,bot,left;
    if (b0!==b1) top  =interp(v0,v1,cellSize,x0,y0,true);
    if (b1!==b2) right=interp(v1,v2,cellSize,x0+cellSize,y0,false);
    if (b2!==b3) bot  =interp(v3,v2,cellSize,x0,y0+cellSize,true);
    if (b3!==b0) left =interp(v0,v3,cellSize,x0,y0,false);
    switch(idx){
      case 1:edges.push([top,left]);break; case 2:edges.push([top,right]);break;
      case 3:edges.push([right,left]);break; case 4:edges.push([right,bot]);break;
      case 5:edges.push([top,left]);edges.push([bot,right]);break;
      case 6:edges.push([top,bot]);break; case 7:edges.push([bot,left]);break;
      case 8:edges.push([left,bot]);break; case 9:edges.push([top,bot]);break;
      case 10:edges.push([top,right]);edges.push([bot,left]);break;
      case 11:edges.push([right,bot]);break; case 12:edges.push([right,left]);break;
      case 13:edges.push([top,right]);break; case 14:edges.push([top,left]);break;
    }
  }
  return edges;
}

function buildVectorPaths(edges) {
  const key=pt=>`${pt.x.toFixed(2)},${pt.y.toFixed(2)}`;
  const ptToEdges=new Map();
  edges.forEach(([p1,p2],idx)=>{
    [p1,p2].forEach(p=>{ const k=key(p); if(!ptToEdges.has(k))ptToEdges.set(k,[]); ptToEdges.get(k).push(idx); });
  });
  const visited=new Set(), paths=[];
  for (let i=0;i<edges.length;i++) {
    if (visited.has(i)) continue;
    let [startP,nextP]=edges[i]; visited.add(i);
    const path=[startP,nextP]; let cur=nextP, curIdx=i;
    while (true) {
      const conn=ptToEdges.get(key(cur))||[];
      let nextIdx=-1, nextPt=null;
      for (const eIdx of conn) {
        if (eIdx===curIdx||visited.has(eIdx)) continue;
        const [ep1,ep2]=edges[eIdx];
        if (key(ep1)===key(cur)){nextPt=ep2;nextIdx=eIdx;break;}
        if (key(ep2)===key(cur)){nextPt=ep1;nextIdx=eIdx;break;}
      }
      if (nextIdx===-1) break;
      visited.add(nextIdx); path.push(nextPt); cur=nextPt; curIdx=nextIdx;
    }
    paths.push(path.length>2?path:[edges[i][0],edges[i][1]]);
  }
  paths.sort((a,b)=>a[0].y-b[0].y);
  return paths;
}

function startVectorDraw(paths, speed) {
  const obj=getDrawer();
  if (!obj){setStatus("❌ مفيش منطقة رسم!",false);return;}
  const {d,rect}=obj;
  const rand=(min,max)=>min+Math.random()*(max-min);
  const events=[]; let t=0;
  paths.forEach(path=>{
    if (!path.length) return;
    t+=rand(80,250);
    events.push({type:"down",x:path[0].x,y:path[0].y,time:t});
    for (let i=1;i<path.length;i++) {
      const prev=path[i-1],cur=path[i];
      const dx=cur.x-prev.x, dy=cur.y-prev.y;
      let dt=Math.max(18,(Math.sqrt(dx*dx+dy*dy)/speed)*1000)+rand(-12,12);
      if (Math.sqrt(dx*dx+dy*dy)<2) dt=Math.max(dt,35);
      t+=dt;
      events.push({type:"move",x:cur.x,y:cur.y,time:t});
    }
    const last=path[path.length-1];
    events.push({type:"up",x:last.x,y:last.y,time:t});
    t+=rand(150,400);
  });
  if (!events.length){setStatus("⚠ مش لافي محيط مساحه",false);return;}

  const start=performance.now(); let idx=0; vectorDrawCancel=false;
  function dispatch(type,x,y) {
    const mk={clientX:rect.left+x,clientY:rect.top+y,preventDefault(){}};
    if(type==="down")d._eventMouseDown(mk);
    else if(type==="move")d._eventMouseMove(mk);
    else d._eventMouseUp(mk);
  }
  function frame() {
    if (vectorDrawCancel){setStatus("⛔  وقف الرسم",false);return;}
    const now=performance.now()-start;
    while (idx<events.length&&events[idx].time<=now) { dispatch(events[idx].type,events[idx].x,events[idx].y); idx++; }
    if (idx>=events.length){setStatus(`✅ تم! (${paths.length} kontur)`,false);vectorAnimationId=null;return;}
    setStatus(`✏ drawing… ${idx}/${events.length}`,true);
    vectorAnimationId=requestAnimationFrame(frame);
  }
  vectorAnimationId=requestAnimationFrame(frame);
  setStatus(`▶ starting (${paths.length} kontur)`,true);
}

function stopVectorDrawing() {
  vectorDrawCancel=true;
  if (vectorAnimationId){cancelAnimationFrame(vectorAnimationId);vectorAnimationId=null;}
}

function processVectorMode(imgEl, threshold, blurSigma, cellSize, speed) {
  const cv=document.createElement("canvas"); cv.width=CW; cv.height=CH;
  const ctx=cv.getContext("2d");
  const scale=Math.min(CW/imgEl.naturalWidth,CH/imgEl.naturalHeight);
  const w=imgEl.naturalWidth*scale, h=imgEl.naturalHeight*scale;
  ctx.fillStyle="#fff"; ctx.fillRect(0,0,CW,CH);
  ctx.drawImage(imgEl,(CW-w)/2,(CH-h)/2,w,h);
  const imageData=ctx.getImageData(0,0,CW,CH);
  const gray=vectorImageToGrayscale(imageData);
  const blurred=vectorGaussianBlur(gray,CW,CH,blurSigma);
  const edges=marchingSquares(blurred,CW,CH,threshold,cellSize);
  if (!edges.length){setStatus("⚠ مفيش محيط -غير الاعدادات ",false);return null;}
  return {paths:buildVectorPaths(edges),speed};
}

// --- خط الانابيب ---

function processImage(imgEl, mode, threshold, contrast, step, colorMode, skipSkin, maxDepth, varThr) {
  if (mode==='quad') return processQuadMode(imgEl,maxDepth,varThr,colorMode);
  if (mode==='vector') return null;
  if (mode==='classic') {
    const processed=processImageClassic(imgEl,threshold,contrast);
    const baseColor=colorMode?hexToRgb(document.getElementById("colorRangeInput").value):null;
    return collectImageSegmentsClassic(processed,step,colorMode,baseColor);
  }
  const raw=getImageData(imgEl);
  const gray=toGray(raw.data,contrast);
  const blurred=gaussianBlur(gray,CW,CH);
  const {bg}=buildBackgroundMaskAuto(blurred,CW,CH);
  return collectPortraitFill(blurred,bg,raw.data,CW,CH,step,skipSkin,colorMode);
}

// --- الوضع الكلاسيكي ---

function startDraw(segs, startFrom=0) {
  const obj=getDrawer();
  if (!obj){setStatus("❌ مفيش مساحه رسم متاحه!",false);return;}
  if (drawing&&startFrom===0){setStatus("⏳ بيرسم!",false);return;}
  if (!segs.length){setStatus("⚠️ مفيش وحدات بكسل للرسم",false);return;}
  const {d,rect}=obj;
  drawing=true; window.__garticBotStop=false; timeoutIds.clear();
  globalSegs=segs; globalColorMode=false; lastIndex=startFrom;
  setStatus(`⏳ ${startFrom} / ${segs.length}`,true);
  let delay=0;
  for (let i=startFrom;i<segs.length;i++) {
    if (window.__garticBotStop) break;
    const s=segs[i];
    sendLine(d,rect,s.x1,s.y1,s.x2,s.y2,delay,s.tool);
    delay+=Math.max(18,CFG.delay+Math.random()*5-2);
    const id=setTimeout(()=>{
      if (window.__garticBotStop) return;
      lastIndex=i+1;
      setStatus(`⏳ ${i+1} / ${segs.length}`,true);
      if (i+1>=segs.length){drawing=false;lastIndex=0;setStatus(`✅تم ! (${segs.length} çizgi)`,false);document.getElementById('gu-resume').style.display='none';}
    },delay);
    timeoutIds.add(id);
  }
}

function startDrawColored(segs, startFrom=0) {
  const obj=getDrawer();
  if (!obj){setStatus("❌ مفيش مساحه رسم متاحه!",false);return;}
  if (drawing&&startFrom===0){setStatus("⏳ بيرسم!",false);return;}
  if (!segs.length){setStatus("⚠️ مفيش وحدات بكسل للرسم",false);return;}
  const {d,rect}=obj;
  drawing=true; window.__garticBotStop=false; timeoutIds.clear();
  globalSegs=segs; globalColorMode=true; lastIndex=startFrom;
  const total=segs.length; let done=startFrom, gDelay=0;
  setStatus(`⏳ ${done} / ${total} ( بيرسم )`,true);
  for (let i=startFrom;i<segs.length;i++) {
    if (window.__garticBotStop) break;
    const s=segs[i];
    if (s.color){
      const cd=gDelay;
      timeoutIds.add(setTimeout(()=>{ if(!window.__garticBotStop)setGarticColor(s.color); },cd));
      gDelay+=30;
    }
    sendLine(d,rect,s.x1,s.y1,s.x2,s.y2,gDelay,s.tool);
    gDelay+=Math.max(18,CFG.delay+Math.random()*5-2);
    timeoutIds.add(setTimeout(()=>{
      if (window.__garticBotStop) return;
      done++; lastIndex=done;
      setStatus(`⏳ ${done} / ${total} (Renkli)`,true);
      if (done>=total){drawing=false;lastIndex=0;setStatus(`✅ تم! ${total} blok (Renkli)`,false);document.getElementById('gu-resume').style.display='none';}
    },gDelay));
  }
}

function setGarticColor(color) {
  const el=document.getElementById("colorRangeInput");
  if (el){el.value=color;el.dispatchEvent(new Event('input',{bubbles:true}));}
}

function drawImage(imgEl, mode, threshold, contrast, step, colorMode, skipSkin, maxDepth, varThr, vectorSettings) {
  setStatus("⚙️ ثواني بيحمل...",true);
  setTimeout(()=>{
    if (mode==='vector') {
      const result=processVectorMode(imgEl,vectorSettings.threshold,vectorSettings.blur,vectorSettings.cellSize,vectorSettings.speed);
      if (result) startVectorDraw(result.paths,result.speed);
      return;
    }
    const segs=processImage(imgEl,mode,threshold,contrast,step,colorMode,skipSkin,maxDepth,varThr);
    setStatus(`📊 ${segs.length} الشريحه جاهزه`,true);
    setTimeout(()=>{ if(colorMode)startDrawColored(segs); else startDraw(segs); },300);
  },50);
}

/* ─────────────────────────── UI ─────────────────────────── */

function setStatus(msg, busy) {
  const el=document.getElementById("gu-status");
  const bim=document.getElementById("gu-draw-img");
  const stp=document.getElementById("gu-stop");
  if (el){el.textContent=msg;el.style.color=busy?"#f4c06a":"#4dffd6";}
  if (bim){bim.disabled=busy;bim.style.opacity=busy?"0.4":"1";}
  if (stp) stp.style.display=busy?"block":"none";
}

function makeDraggable(panel) {
  let sx,sy,sl,st,drag=false;
  function onStart(ex,ey){sx=ex;sy=ey;const r=panel.getBoundingClientRect();sl=r.left;st=r.top;drag=true;}
  function onMove(ex,ey){if(!drag)return;panel.style.left=Math.max(0,sl+ex-sx)+"px";panel.style.top=Math.max(0,st+ey-sy)+"px";panel.style.right="auto";panel.style.bottom="auto";}
  function onEnd(){drag=false;}
  const hdr=document.getElementById("gu-handle");
  hdr.addEventListener("mousedown",e=>{onStart(e.clientX,e.clientY);e.preventDefault();});
  document.addEventListener("mousemove",e=>onMove(e.clientX,e.clientY));
  document.addEventListener("mouseup",onEnd);
  hdr.addEventListener("touchstart",e=>{const t=e.touches[0];onStart(t.clientX,t.clientY);},{passive:true});
  document.addEventListener("touchmove",e=>{if(!drag)return;const t=e.touches[0];onMove(t.clientX,t.clientY);},{passive:true});
  document.addEventListener("touchend",onEnd);
}

function toggleMinimize() {
  const body=document.getElementById("gu-body");
  const btn=document.getElementById("gu-min-btn");
  const c=body.style.display==="none";
  body.style.display=c?"block":"none";
  if(btn)btn.textContent=c?"▾":"▸";
}

//* ───RGB ال ───────────────────────────────────────────
const CSS = `
  @import url('https://fonts.googleapis.com/css2?family=Cairo:wght@900&display=swap');

  /* أنيميشن قوس قزح منوع (7 ألوان) للحدود */
  @keyframes rainbow-border {
    0% { border-color: #ff0000; box-shadow: 0 0 25px #ff0000; }     /* أحمر */
    15% { border-color: #ff8000; box-shadow: 0 0 25px #ff8000; }    /* برتقالي */
    30% { border-color: #ffff00; box-shadow: 0 0 25px #ffff00; }    /* أصفر */
    45% { border-color: #00ff00; box-shadow: 0 0 25px #00ff00; }    /* أخضر */
    60% { border-color: #00ffff; box-shadow: 0 0 25px #00ffff; }    /* أزرق فاتح */
    75% { border-color: #0000ff; box-shadow: 0 0 25px #0000ff; }    /* أزرق */
    90% { border-color: #8b00ff; box-shadow: 0 0 25px #8b00ff; }    /* بنفسجي */
    100% { border-color: #ff0000; box-shadow: 0 0 25px #ff0000; }
  }

  /* أنيميشن النص الـ RGB */
  @keyframes rainbow-text {
    0% { color: #ff0000; } 15% { color: #ff8000; } 30% { color: #ffff00; }
    45% { color: #00ff00; } 60% { color: #00ffff; } 75% { color: #0000ff; }
    90% { color: #8b00ff; } 100% { color: #ff0000; }
  }

  #gu-panel {
    position: fixed; right: 20px; bottom: 20px;
    width: 350px; /* كبرنا العرض */
    background: #000000; /* خلفية سوداء فخمة */
    border: 6px solid #ff0000; border-radius: 35px; /* حدود ضخمة وكرتونية */
    z-index: 999999; font-family: 'Cairo', sans-serif;
    color: #fff; direction: rtl;
    animation: rainbow-border 5s infinite linear;
    box-shadow: 0 15px 50px rgba(0,0,0,1);
    display: flex; flex-direction: column;
    overflow: hidden;
  }

  #gu-handle {
    padding: 20px; background: rgba(255,255,255,0.05);
    text-align: center; cursor: move; border-bottom: 2px solid rgba(255,255,255,0.1);
  }

  .main-title {
    font-size: 40px; /* حجم كبير جداً */
    font-weight: 900;
    animation: rainbow-text 3s infinite linear;
    text-shadow: 5px 5px #000, -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000, 2px 2px 0 #000; /* Stroke كرتوني */
    margin: 0;
    letter-spacing: -1px;
  }

  #gu-body { padding: 20px; max-height: 75vh; overflow-y: auto; scrollbar-width: none; }
  #gu-body::-webkit-scrollbar { display: none; }

  .gu-label { font-size: 15px; color: #ffff00; font-weight: bold; display: block; margin-bottom: 8px; }

  .gu-input, .gu-select {
    width: 100%; padding: 12px; background: #111;
    border: 3px solid #333; border-radius: 15px;
    color: #00ff00; font-family: 'Cairo'; margin-bottom: 15px; font-weight: bold;
  }

  /* الزراير الخضراء الكرتونية */
  .gu-btn {
    width: 100%; padding: 16px; margin-top: 12px;
    border: none; border-radius: 20px; font-weight: 900;
    font-size: 20px; cursor: pointer;
    transition: 0.2s; text-transform: uppercase;
    box-shadow: 0 6px 0 #006600; /* ظل كرتوني */
  }

  #gu-draw-img {
    background: #00ff00; color: #000;
  }
  #gu-draw-img:hover { background: #00cc00; transform: translateY(-2px); box-shadow: 0 8px 0 #004400; }
  #gu-draw-img:active { transform: translateY(4px); box-shadow: 0 2px 0 #004400; }

  #gu-stop { background: #ff0000; color: #fff; box-shadow: 0 6px 0 #880000; }
  #gu-resume { background: #0088ff; color: #fff; box-shadow: 0 6px 0 #004488; }
  #gu-clear { background: #444; color: #fff; font-size: 14px; box-shadow: 0 4px 0 #222; }

  .url-row { display: flex; gap: 8px; margin-bottom: 15px; }
  #gu-img-url-load { background: #00ff00; border: none; border-radius: 12px; width: 60px; color: #000; font-weight: 900; cursor: pointer; }

  .skin-box {
    background: #111; padding: 15px;
    border-radius: 20px; margin-bottom: 15px; border: 2px solid #222;
  }

  .fares-credit {
    font-size: 12px; color: #555; text-align: left;
    padding: 10px 20px; font-weight: bold; letter-spacing: 2px;
    animation: rainbow-text 10s infinite linear;
  }
`;

const styleTag = document.createElement("style");
styleTag.textContent = CSS;
document.head.appendChild(styleTag);

const panel = document.createElement("div");
panel.id = "gu-panel";

panel.innerHTML = `
<div id="gu-handle">
    <div id="gu-min-btn" style="position: absolute; left: 20px; top: 20px; cursor: pointer; font-size: 20px;">▾</div>
    <h1 class="main-title">Gartic Drawer</h1>
    <div style="font-size: 12px; color: #fff; font-weight: bold; letter-spacing: 3px; opacity: 0.5;"> CREATED BY F̷a̷r̷e̷s̷</div>
  </div>

    <div id="gu-drop-zone" style="border: 4px dashed #333; padding: 25px; text-align: center; border-radius: 20px; cursor: pointer; color: #555; margin-bottom: 15px; font-weight: bold; transition: 0.3s;">
      📸 ارفع صورتك هنا
      <div style="font-size: 10px; color: #444; margin-top: 5px;">(أو اسحبها وافلتها هنا)</div>
    </div>
    <input type="file" id="gu-file-input" style="display:none" accept="image/*">


    <label class="gu-label">رابط الصورة</label>
    <div class="url-row">
      <input type="text" id="gu-img-url" class="gu-input" placeholder="https://..." style="margin-bottom:0">
      <button id="gu-img-url-load">GO</button>
    </div>

    <label class="gu-label">طريقة الرسم</label>
    <select id="gu-mode" class="gu-select">
      <option value="classic">⬛ خطوط عادية</option>
      <option value="fast" selected>⚡ رسم سريع جداً</option>
      <option value="quad">🟦 مربعات (بكسل)</option>
      <option value="vector">🎯 تحديد أطراف</option>
    </select>

    <div class="skin-box">
      <label class="gu-label">الحساسية: <span id="gu-thr-val" style="color:#0f0">240</span></label>
      <input type="range" id="gu-thr" class="gu-input" style="padding:0; accent-color:#0f0;" min="20" max="240" value="240">

      <label class="gu-label">التباين: <span id="gu-con-val" style="color:#0f0">3.3</span></label>
      <input type="range" id="gu-con" class="gu-input" style="padding:0; accent-color:#0f0;" min="0.5" max="4" step="0.1" value="3.3">
    </div>

    <label class="gu-label">سرعة الخطوة:</label>
    <input type="range" id="gu-img-step" class="gu-input" style="padding:0; accent-color:#0f0;" min="2" max="20" value="5">

    <label class="gu-label">التأخير (ms):</label>
    <input type="number" id="gu-img-delay" class="gu-input" value="120">

    <div class="skin-box">
      <label style="display:flex; align-items:center; gap:10px; cursor:pointer;">
        <input type="checkbox" id="gu-skip-skin" checked style="width:20px; height:20px; accent-color:#0f0;">
        <span style="color: #0f0; font-weight: bold; font-size:16px;">🎭 تخطي الوجوه</span>
      </label>
    </div>

    <div style="background: #111; padding: 15px; border-radius: 20px; border: 2px solid #222; margin-bottom: 15px;">
      <label class="gu-label">اللون اليدوي</label>
      <input type="color" id="colorRangeInput" value="#00ff00" style="width:100%; height:40px; border:none; background:none; cursor:pointer;">
    </div>

    <label style="display:flex; align-items:center; gap:10px; cursor:pointer; margin-bottom:20px; background:rgba(0,255,0,0.1); padding:10px; border-radius:15px;">
      <input type="checkbox" id="gu-color-mode" checked style="width:20px; height:20px; accent-color:#0f0;">
      <span style="color: #0f0; font-weight: bold; font-size:18px;">🌈 تشغيل الألوان (RGB)</span>
    </label>

    <button class="gu-btn" id="gu-draw-img">▶ ابـدأ الـرسم</button>
    <button class="gu-btn" id="gu-stop">⛔ وقـف</button>
    <button class="gu-btn" id="gu-resume">▶ كـمـل</button>
    <button class="gu-btn" id="gu-clear">🗑️ امسح اللوحة</button>

    <div id="gu-status" style="text-align: center; color: #fff; margin-top: 15px; font-weight:bold; text-shadow: 2px 2px #000;">النظام شغال </div>
  </div>
  <div class="fares-credit">Created By F̷a̷r̷e̷s̷</div>
 </div>
`;

document.body.appendChild(panel);

makeDraggable(panel);
document.getElementById("gu-min-btn").addEventListener("click", toggleMinimize);

      /* ─── ربط الأحداث (التصليح النهائي) ─── */
    let loadedImg = null;
    const fileInput = document.getElementById("gu-file-input");
    const dropZone = document.getElementById("gu-drop-zone");

    // وظيفة التعامل مع الملفات
    const handleFile = (file) => {
        if (!file) return;
        const reader = new FileReader();
        reader.onload = (e) => {
            const img = new Image();
            img.onload = () => {
                loadedImg = img;
                setStatus("🖼️ الصورة جاهزة", false);
                dropZone.style.borderColor = "#00ff00";
            };
            img.src = e.target.result;
        };
        reader.readAsDataURL(file);
    };

    dropZone.onclick = () => fileInput.click();
    fileInput.onchange = (e) => handleFile(e.target.files[0]);

    // تحميل من رابط
    document.getElementById("gu-img-url-load").onclick = () => {
        const url = document.getElementById("gu-img-url").value;
        if (!url) return;
        const img = new Image();
        img.crossOrigin = "Anonymous";
        img.onload = () => {
            loadedImg = img;
            setStatus("🖼️ تم تحميل الرابط", false);
        };
        img.onerror = () => setStatus("❌ فشل تحميل الرابط", false);
        img.src = url;
    };

    // زرار الرسم الكبير
    document.getElementById("gu-draw-img").onclick = () => {
        if (!loadedImg) {
            setStatus("📂 ارفع الصورة الأول  !", false);
            return;
        }

        const mode = document.getElementById("gu-mode").value;
        CFG.delay = parseInt(document.getElementById("gu-img-delay").value) || 120;

        const vectorSettings = {
            threshold: parseInt(document.getElementById("gu-thr").value),
            blur: 1.2,
            cellSize: 4,
            speed: 500
        };

        drawImage(
            loadedImg,
            mode,
            parseInt(document.getElementById("gu-thr").value),
            parseFloat(document.getElementById("gu-con").value),
            parseInt(document.getElementById("gu-img-step").value),
            document.getElementById("gu-color-mode").checked,
            document.getElementById("gu-skip-skin").checked,
            8,    // quad depth
            1500, // quad var
            vectorSettings
        );
    };

    // التحكم
    document.getElementById("gu-stop").onclick = stopDrawing;
    document.getElementById("gu-resume").onclick = resumeDrawing;
    document.getElementById("gu-clear").onclick = clearCanvas;

    // تحديث أرقام السلايدرز
    document.getElementById("gu-thr").oninput = (e) => document.getElementById("gu-thr-val").textContent = e.target.value;
    document.getElementById("gu-con").oninput = (e) => document.getElementById("gu-con-val").textContent = e.target.value;

})(); // قفلة الـ Function الأساسية